페이지는 준비됐고 배포 버튼은 한 번의 클릭이면 충분합니다. 그리고 여러분의 <head>를 곧 네 종류의 청중이 읽게 됩니다 ── Google 크롤러, Facebook unfurl 봇, Twitter 카드 스크레이퍼, Discord 링크 미리보기. 누구도 사람처럼 렌더링된 HTML 본문을 보지 않습니다. 그들은 메타 태그만 봅니다.

완전한 <head> 블록을 지금 생성하기 →

페이지 메타데이터의 네 가지 레이어

현대 페이지 head는 네 가지 독립적인 질문에 답합니다:

레이어청중핵심 태그
기본 SEO검색 엔진, 브라우저title, description, canonical, robots, viewport
Open GraphFacebook, LinkedIn, Slack, iMessage, Discordog:title, og:description, og:image, og:url, og:type
Twitter 카드Twitter / Xtwitter:card, twitter:image, twitter:site
Schema.org JSON-LDGoogle 리치 결과, 음성 어시스턴트<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/post
  • https://example.com/post와 https://www.example.com/post
  • ?utm_source=twitter와 파라미터 없는 URL
  • https://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 Debuggerog:* 태그, 스크레이프 결과, 캐시된 미리보기https://developers.facebook.com/tools/debug/
X(Twitter) Card Validator미리보기 기능은 없어짐. 테스트 계정으로 게시해 카드를 확인https://cards-dev.twitter.com/validator
LinkedIn Post InspectorLinkedIn 관점에서 og:* 태그 렌더링https://www.linkedin.com/post-inspector/
Google Rich Results TestJSON-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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  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 내부에 이스케이프되지 않은 따옴표가 있으면 블록 전체가 조용히 깨집니다.

관련 도구

참고 자료