页面写好了,部署按钮就差一下点击,而你的 <head> 即将被四类受众读到:Google 的爬虫、Facebook 的 unfurl 机器人、Twitter 的卡片抓取器、Discord 的链接预览。它们都不像人那样看渲染好的 HTML 正文——它们看的是 meta 标签。

立即生成完整 <head> 区块 →

页面元数据的四个层次

现代页面 head 回答四个独立问题:

层级受众核心标签
基础 SEO搜索引擎、浏览器title、description、canonical、robots、viewport
Open GraphFacebook、LinkedIn、Slack、iMessage、Discordog:title、og:description、og:image、og:url、og:type
Twitter CardTwitter / Xtwitter:card、twitter:image、twitter:site
Schema.org JSON-LDGoogle 富结果、语音助手<script type="application/ld+json">

这些层级并不冗余。每一层都由不同厂商设计、回答其他层级不答的问题。搜索引擎不读 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 必须是带 scheme 与 host 的绝对 URL。/og/article.png 这样的相对路径不是合法取值,抓取器也不会像浏览器那样去补全它。

可达性。抓取器按需拉取这个 URL。如果你的 CDN 慢、被 CAPTCHA 挡住、或返回的 Content-Type 错成 text/html 而不是 image/png,预览就会回退到一个通用站点缩略图。

缓存。Facebook 会缓存抓取结果。修好 og:image 之后,到 Sharing Debugger 点「Scrape Again」强制刷新。

每张图都要同时声明宽高:

<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 告诉 Facebook 一次分享归到哪个 URL。

同一篇文章出现多个 URL 的常见原因:

  • https://example.com/post/ 和 https://example.com/post
  • https://example.com/post 和 https://www.example.com/post
  • ?utm_source=twitter 与不带参数的版本
  • https://example.com/post 和 https://example.com/post?ref=newsletter

没有 og:url 时,其中两个被分享出去,Facebook 可能把它们当成不同链接,各自累计分享数。在每个变体上把 og:url 设为 canonical URL,Facebook 就会把分享都算到这一个 URL 上。

如果路由带尾部斜杠,canonical URL 也必须带。ZeroTool 的静态构建始终输出尾部斜杠,canonical 不一致就会破坏去重。

不依赖 schema 库手写 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 Inspector模拟 LinkedIn 渲染 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 的时候。

内联生成

如果你直接在模板引擎里输出 meta 标签,逻辑足够小可以内联保留。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 里未转义的引号会让整个区块静默失效。

相关工具

参考资料