페이지는 준비됐고 배포 버튼은 한 번의 클릭이면 충분합니다. 그리고 여러분의 <head>를 곧 네 종류의 청중이 읽게 됩니다 ── Google 크롤러, Facebook unfurl 봇, Twitter 카드 스크레이퍼, Discord 링크 미리보기. 누구도 사람처럼 렌더링된 HTML 본문을 보지 않습니다. 그들은 메타 태그만 봅니다.
페이지 메타데이터의 네 가지 레이어
현대 페이지 head는 네 가지 독립적인 질문에 답합니다:
| 레이어 | 청중 | 핵심 태그 |
|---|---|---|
| 기본 SEO | 검색 엔진, 브라우저 | title, description, canonical, robots, viewport |
| Open Graph | Facebook, LinkedIn, Slack, iMessage, Discord | og:title, og:description, og:image, og:url, og:type |
| Twitter 카드 | Twitter / X | twitter:card, twitter:image, twitter:site |
| Schema.org JSON-LD | Google 리치 결과, 음성 어시스턴트 | <script type="application/ld+json"> |
이 레이어들은 중복이 아닙니다. 각 레이어는 다른 벤더가 설계했고 다른 레이어가 답하지 않는 질문에 답합니다. 검색 엔진은 unfurl을 위해 Open Graph를 읽지 않고, Facebook은 공유 미리보기를 위해 Schema.org를 읽지 않습니다. 한 레이어를 빼면 그 청중은 추측에 의존하게 되고, 대개 잘못 추측합니다.
소셜 카드의 필수 다섯 줄
Open Graph 프로토콜은 og:title, og:type, og:image, og:url 네 가지를 필수 속성으로 정한다. 여기에 대부분의 미리보기가 제목 아래 보여 주는 설명을 더한다:
<meta property="og:title" content="Article title">
<meta property="og:type" content="article">
<meta property="og:image" content="https://example.com/og/article.png">
<meta property="og:url" content="https://example.com/article/">
<meta name="description" content="One-sentence pitch under 160 chars.">
다섯 줄만 쓸 시간이 있다면 이 다섯 줄을 쓰자.
og:image: 누구나 한 번은 걸리는 사양
Slack이나 Discord 미리보기가 이상하게 보이는 가장 흔한 이유는 og:image 문제입니다. 이 사양은 관대하지 않습니다:
크기. 1200x630 픽셀(1.91:1)이 Facebook, LinkedIn, Discord에서 가장 안정적으로 렌더링되는 기본값입니다. Twitter의 summary_large_image는 2:1을 기대하므로 1200x600도 잘 동작합니다. 200x200 미만 이미지는 일부 스크레이퍼가 즉시 거부합니다.
형식. PNG나 JPEG가 무난하다. X의 카드 이미지는 JPG, PNG, WEBP, GIF를 지원하며, SVG는 웹에서 유효한 이미지이지만 X와 Facebook은 지원하지 않는다.
절대 URL. og:image는 스킴과 호스트가 있는 절대 URL이어야 한다. /og/article.png 같은 상대 경로는 올바른 값이 아니며, 스크레이퍼는 브라우저처럼 보완해 주지 않는다.
도달 가능성. 스크레이퍼는 필요할 때 그 URL을 가져옵니다. CDN이 느리거나, CAPTCHA로 막혀 있거나, 이미지가 image/png가 아닌 text/html로 반환되면 미리보기는 일반 사이트 썸네일로 폴백됩니다.
캐시. Facebook은 스크랩 결과를 캐시한다. og:image를 고친 뒤 공유 디버거에서 “다시 스크랩”을 눌러 새로 가져오게 하자.
이미지의 너비와 높이는 항상 함께 선언합니다:
<meta property="og:image" content="https://example.com/og/article.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="글 커버, 녹색 배경에 큰 흰색 세리프체로 제목이 표시됨.">
크기를 선언해 두면 스크레이퍼가 이미지를 먼저 내려받지 않고도 미리보기를 배치할 수 있고, og:image:alt는 이미지를 볼 수 없는 사람을 위해 내용을 설명한다.
Canonical URL: 잘못된 값은 공유 수를 분산시킵니다
<link rel="canonical">과 og:url은 별개의 태그이며 같은 값을 넣어야 한다. canonical은 검색 엔진에 어떤 URL이 정식인지 알리고, og:url은 같은 페이지에 여러 경로로 접근할 수 있을 때 공유를 어느 URL로 모을지 Facebook에 알린다.
같은 글이 여러 URL을 갖게 되는 흔한 이유:
https://example.com/post/와https://example.com/posthttps://example.com/post와https://www.example.com/post?utm_source=twitter와 파라미터 없는 URLhttps://example.com/post와https://example.com/post?ref=newsletter
og:url 없이 이 중 두 개가 공유되면 Facebook은 이를 서로 다른 링크로 보고 공유 수를 따로 셀 수 있다. 모든 변형에서 og:url을 정식 URL로 두면 Facebook은 공유를 그 하나의 URL로 모은다.
라우팅이 후행 슬래시를 사용한다면 canonical URL에도 슬래시를 붙여야 합니다. ZeroTool의 정적 빌드는 항상 후행 슬래시를 출력하므로, canonical 불일치는 중복 제거를 무너뜨립니다.
스키마 라이브러리 없이 JSON-LD 작성하기
JSON-LD는 그저 <script type="application/ld+json"> 안에 들어가는 JSON일 뿐입니다. Google 리치 결과 파서는 필드 순서와 공백에는 관대하지만 몇 가지에는 엄격합니다:
"@context": "https://schema.org"는 필수."@type"은 필수이며 Schema.org의 타입 목록 중 하나와 일치해야 합니다.- 모든 URL은 절대 URL이어야 합니다.
- 날짜는 ISO 8601(
2026-05-05,May 5 2026이 아님). - 여러 값을 가질 수 있는 필드(
author등)는 배열로 쓸 수 있다. 값이 하나면 그대로 써도 된다.
최소한의 Article:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Meta Tag Generator: One Page, Four Audiences",
"description": "Build a complete head meta block...",
"url": "https://example.com/blog/meta-tag-generator-guide/",
"image": "https://example.com/og/article.png",
"datePublished": "2026-05-05",
"author": { "@type": "Person", "name": "Jane Doe" }
}
</script>
Schema.org가 문서화한 모든 필드를 채울 필요는 없습니다. Google의 리치 결과 적격성 가이드가 결과 유형별 필수 필드를 알려줍니다 ── 거기서 시작하고, 실제 데이터가 있을 때만 나머지를 추가하세요.
배포한 내용을 검증하기
네 가지 무료 도구가 네 종류의 청중을 커버합니다:
| 도구 | 검사 대상 | URL |
|---|---|---|
| Facebook Sharing Debugger | og:* 태그, 스크레이프 결과, 캐시된 미리보기 | https://developers.facebook.com/tools/debug/ |
| X(Twitter) Card Validator | 미리보기 기능은 없어짐. 테스트 계정으로 게시해 카드를 확인 | https://cards-dev.twitter.com/validator |
| LinkedIn Post Inspector | LinkedIn 관점에서 og:* 태그 렌더링 | https://www.linkedin.com/post-inspector/ |
| Google Rich Results Test | JSON-LD 적격성, 구조화 데이터 경고 | https://search.google.com/test/rich-results |
Discord와 Slack은 공개 디버거가 없습니다 ── 가져와 공격적으로 캐싱하므로, 가장 직접적인 방법은 비공개 채널에 메시지를 붙여서 실제 모습을 확인하는 것입니다.
다섯 번째 점검은 view-source:(Chrome에서 Cmd+Option+U). 중복된 og:title, 누락된 og:image, 그리고 content가 문자 그대로 undefined인 태그를 살피세요 ── 마지막은 템플릿 엔진이 빈 변수를 삼킨 흔적입니다.
흔한 함정
og:image 여러 개. Open Graph 프로토콜은 이를 배열로 허용하며, 충돌하면 첫 번째를 우선한다. 보여 주고 싶은 이미지를 맨 앞에 두자. og:image:secure_url은 같은 이미지의 HTTPS 주소이지 작은 버전이 아니다.
og:url이 다른 페이지를 가리킴. Facebook은 og:url의 페이지로 미리보기를 만들기 때문에, 템플릿이 홈페이지 URL을 og:url에 남겨 두면 어떤 글을 공유해도 홈페이지처럼 보인다.
*robots: noindex를 설정하고도 og:가 노출되리라 기대. og:*는 동작합니다 ── 소셜 스크레이퍼는 robots를 읽지 않습니다 ── 그러나 검색 엔진은 페이지를 색인하지 않으므로 SERP의 리치 미리보기에는 절대 나타나지 않습니다.
하드코딩한 og:image:width가 실제 파일과 다름. 선언한 크기는 이미지와 맞추거나 아예 빼자. 그래야 미리보기가 잘못된 비율로 배치되지 않는다.
JSON-LD의 끝 쉼표나 이스케이프하지 않은 따옴표. 올바르지 않은 JSON이라 JSON.parse가 거부하고, Google 리치 결과 테스트도 구조화된 데이터 대신 파싱 오류를 보고한다. 배포 전에 JSON 린터로 확인하자.
두 페이지의 canonical URL이 같음. canonical은 검색 엔진에 두 페이지가 중복이라고 알리므로 보통 한쪽만 색인된다. CMS 템플릿이 페이지가 나뉜 아카이브에서 canonical을 갱신하지 않을 때 흔히 생긴다.
인라인으로 생성하기
템플릿 엔진에서 직접 메타 태그를 출력한다면 로직은 인라인으로 유지할 만큼 작습니다. JavaScript 템플릿:
function metaBlock({ title, description, canonical, image, type = 'website' }) {
const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
return `
<title>${esc(title)}</title>
<meta name="description" content="${esc(description)}">
<link rel="canonical" href="${esc(canonical)}">
<meta property="og:type" content="${esc(type)}">
<meta property="og:title" content="${esc(title)}">
<meta property="og:description" content="${esc(description)}">
<meta property="og:url" content="${esc(canonical)}">
<meta property="og:image" content="${esc(image)}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${esc(title)}">
<meta name="twitter:description" content="${esc(description)}">
<meta name="twitter:image" content="${esc(image)}">
`.trim();
}
이스케이프가 핵심입니다 ── title 내부에 이스케이프되지 않은 따옴표가 있으면 블록 전체가 조용히 깨집니다.
관련 도구
- Robots.txt 생성기 — 메타 태그와 함께 크롤 규칙 설정
- Favicon 생성기 —
<head>에서 참조하는 아이콘 세트 생성 - URL 파서 — 배포 전에 canonical URL 형식 확인
참고 자료
- Open Graph protocol — 원본 사양
- Twitter — Cards Markup —
twitter:*레퍼런스 - Schema.org — 시작하기
- Google — Control your snippets —
description이 스니펫이 되는 과정