页面写好了,部署按钮就差一下点击,而你的 <head> 即将被四类受众读到:Google 的爬虫、Facebook 的 unfurl 机器人、Twitter 的卡片抓取器、Discord 的链接预览。它们都不像人那样看渲染好的 HTML 正文——它们看的是 meta 标签。
页面元数据的四个层次
现代页面 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 Card | Twitter / X | twitter:card、twitter:image、twitter:site |
| Schema.org JSON-LD | Google 富结果、语音助手 | <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/posthttps://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 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 的时候。
内联生成
如果你直接在模板引擎里输出 meta 标签,逻辑足够小可以内联保留。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 生成器 — 配合 meta 标签设置爬虫规则
- Favicon 生成器 — 生成
<head>引用的图标包 - URL 解析器 — 发布前验证 canonical URL 是否合规
参考资料
- Open Graph protocol — 原始规范
- Twitter — Cards Markup —
twitter:*参考 - Schema.org — 入门
- Google — Control your snippets —
description如何变成搜索片段