你必须写对的 OG / Twitter meta 标签
「要写 Open Graph」这件事大家都知道。真正翻车的,多半不是没听过协议,而是 DevTools 里看起来齐全,爬虫却仍看到相对路径图片、写错的 name=、两个互相打架的 title,或根本没有 Twitter Card。
本文是最小正确集:一段可粘贴的 HTML、逐字段说明与反例、与 <title> / meta description / JSON-LD 的回退关系、框架侧点到为止,以及用稳定 规则 id 做的自检清单。
更完整的工作流(预览 vs 官方 rescrape vs CI)见 Open Graph 到底在解决什么;空白卡片见 为什么你的 Open Graph 卡片一片空白。
复制即用的最小片段
ogp.me 协议要求的四件套:og:title、og:type、og:url、og:image。上线实务几乎总要补 og:description、og:site_name,再加 twitter:card,好让 X 与部分客户端选对布局。
<head>
<title>发版说明 · Acme</title>
<meta
name="description"
content="本周修了什么,以及对你意味着什么。"
/>
<link rel="canonical" href="https://example.com/blog/ship-notes" />
<!-- Open Graph(用 property=,不要用 name=) -->
<meta property="og:title" content="发版说明:本周修了什么" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://example.com/blog/ship-notes" />
<meta
property="og:image"
content="https://example.com/og/ship-notes-1200x630.jpg"
/>
<meta
property="og:description"
content="本周修了什么,以及分享链接时同事会看到什么。"
/>
<meta property="og:site_name" content="Acme" />
<!-- Twitter / X -->
<meta name="twitter:card" content="summary_large_image" />
<!-- 若不需要单独覆盖 OG 回退,可省略:
<meta name="twitter:title" content="..." />
<meta name="twitter:description" content="..." />
<meta name="twitter:image" content="https://example.com/og/ship-notes-1200x630.jpg" />
-->
</head>
这段里的硬约束:
- 图片与 URL 必须是绝对 HTTPS(协议 + 主机 + 路径)。
og:url有意与rel=canonical一致。- Open Graph 用
property="og:...";Twitter Card 惯例用name="twitter:..."。 - 每个 key 只保留一个权威值。不要让主题、插件、框架各写一份不同的
og:title。
逐字段说明(与反例)
og:title — og-title-missing
卡片标题。优先写「信息流里人愿意点」的句子,而不是堆关键词的 SEO 串。
| 反例 | 后果 |
|---|---|
| 缺失 / 空 | 回退到 <title> 或裸 URL |
永远照抄 <title> |
标签页与信息流职责不同——有时可以共用;默认照抄等于放弃写分享向标题(og-title-matches-title,info) |
<!-- 弱:CMS slug 当标题 -->
<meta property="og:title" content="ship-notes-q3-final-v2" />
<!-- 更好:人话标题 -->
<meta property="og:title" content="发版说明:本周修了什么" />
og:type — og-type-missing
告诉爬虫这是什么对象。落地页:website;博客:article;商品页在真正是商品时用 product。
| 反例 | 后果 |
|---|---|
| 缺失 | 部分富对象处理降级 |
自造值("landing"、"page") |
当未知类型处理,没有收益 |
<meta property="og:type" content="website" />
<!-- 或 -->
<meta property="og:type" content="article" />
og:url — og-url-missing
分享的规范身份。点赞、展开会按它聚合 http/https、尾斜杠、追踪参数等变体。
| 反例 | 规则 / 影响 |
|---|---|
| 缺失 | 互动散落在多种 URL 上 |
相对路径(/blog/post) |
og-url-relative |
站点已是 HTTPS 仍写 http:// |
og-url-not-https |
与 rel=canonical 不一致 |
og-url-canonical-mismatch |
<!-- 错误 -->
<meta property="og:url" content="/blog/ship-notes" />
<!-- 正确 -->
<meta property="og:url" content="https://example.com/blog/ship-notes" />
og:image — og-image-missing
没有可用图时,不少平台只剩文字行,甚至几乎没有卡片样式。尽量用专用分享图(约 1200×630,约 1.91:1),不要拿 logo 凑数。
| 反例 | 规则 / 影响 |
|---|---|
| 缺失 / 空 | 空白或几乎无视觉权重 |
| 相对路径 | og-image-relative-url——经常被直接丢掉 |
| 非 HTTPS | og-image-not-https |
图片工程(体积、跳转、Content-Type、裁切风险)留给系列后文。先把绝对 URL 写对。
og:description — og-description-missing
不在协议四件套里,但实务上是 warn 级必补。没有它时,客户端会回退 meta description 或抓正文——信息流文案变成抽奖。
写一两句给「正在刷时间线的人」看(大约 70–200 字符作起点;各平台截断不同)。
<!-- CMS 空占位往往比认真省略更糟 -->
<meta property="og:description" content="" />
og:site_name — og-site-name-missing
若干平台上的发布者标签。写品牌名,不要写页面标题。
<meta property="og:site_name" content="Acme" />
twitter:card — twitter-card-missing / twitter-card-invalid
X 仍靠 twitter:card 选布局。合法值:summary、summary_large_image、app、player。未知值等同缺失。
营销页与博客多半用 summary_large_image。
<meta name="twitter:card" content="summary_large_image" />
回退现实: 很多客户端在缺少对应 twitter:* 时会用 og:title / og:description / og:image。这很省事——但不能因此省略 twitter:card。卡片类型并不能完全由 OG 推断出来。
若只想在 X 上用不同图或标题,再单独写 twitter:title、twitter:description、twitter:image。否则让 OG 承载文案,仍声明 card 类型。
属性名与重复值陷阱
两类 bug 每周都在上线:
namevsproperty—— OG 必须用property="og:..."。写成name="og:title"是常见粘贴错误;主流解析器会直接忽略(meta-attribute-wrong)。- 冲突的重复 —— 主题 + 插件 + 应用各输出一份不同的
og:title;爬虫取 first 还是 last 不可预期(meta-tag-conflicting-duplicates)。
<!-- 错:属性名不对 -->
<meta name="og:title" content="View Source 里看起来对,实际无效" />
<!-- 错:两个赢家 -->
<meta property="og:title" content="来自 CMS" />
<meta property="og:title" content="来自主题默认值" />
与 title / description / JSON-LD 的关系(回退链)
把它想成回退链,而不是单一真相源:
| 卡片字段 | 优先 | 常见回退 |
|---|---|---|
| 标题 | og:title |
<title>,再是 URL |
| 正文 | og:description |
meta name="description",再是抓取正文 |
| 图 | og:image |
经常没有——别赌平台会找 logo |
| X 布局 | twitter:card |
无合法类型则降级 / 跳过 |
| X 标题 / 图 | twitter:title / twitter:image |
有 OG 时通常回退到 og:* |
JSON-LD(Article、Product 等)服务搜索与富结果,不能可靠替代 Open Graph 的分享卡片。若某平台 unfurl 根本不读你的 JSON-LD,schema 写得再漂亮,Slack 仍可能没图。
实务分工:
<title>+ meta description → 标签页与搜索摘要og:*(+twitter:card)→ 分享表面- JSON-LD → 读它的引擎的结构化数据
事实对齐(产品名、日期、URL),但不要假设一层会填满另外两层。
框架侧(点到为止)
Next.js Metadata
Next 的 metadata / generateMetadata 在同时填 openGraph 与 twitter 时映射清晰:
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "发版说明 · Acme",
description: "本周修了什么,以及对你意味着什么。",
alternates: { canonical: "https://example.com/blog/ship-notes" },
openGraph: {
title: "发版说明:本周修了什么",
description: "本周修了什么,以及分享链接时同事会看到什么。",
url: "https://example.com/blog/ship-notes",
siteName: "Acme",
type: "article",
images: [
{
url: "https://example.com/og/ship-notes-1200x630.jpg",
width: 1200,
height: 630,
},
],
},
twitter: {
card: "summary_large_image",
// 省略 title / description / images 时通常回退到 openGraph
},
};
注意图片绝对 URL(或配置好的 metadataBase),并在 layout 合并后检查渲染出的 HTML里每个 property 仍只有一个值。
CMS 字段映射
| 编辑常填的字段 | 应输出为 |
|---|---|
| SEO 标题 / 浏览器标题 | <title> |
| SEO 描述 | meta name="description" |
| 分享标题 / 社交标题 | og:title(可选再写 twitter:title) |
| 分享描述 | og:description |
| 分享图 / 社交图 | 绝对 URL 的 og:image |
| 站点 / 品牌名(全局) | og:site_name |
| 规范 URL | rel=canonical 与 og:url |
若 CMS 只有「SEO 标题」没有分享标题,标签页与信息流会完全一样。可作默认,但不是每篇发布文的最优解。
用规则 id 自检
对爬虫会看到的原始 HTML逐项核对(或放进 CI):
| 检查 | 规则 |
|---|---|
有 og:title |
og-title-missing |
有 og:type |
og-type-missing |
有绝对 HTTPS 的 og:url |
og-url-missing、og-url-relative、og-url-not-https |
og:url ≡ canonical |
og-url-canonical-mismatch |
有绝对 HTTPS 的 og:image |
og-image-missing、og-image-relative-url、og-image-not-https |
有 og:description |
og-description-missing |
有 og:site_name |
og-site-name-missing |
有合法的 twitter:card |
twitter-card-missing、twitter-card-invalid |
OG 用 property= 而非 name= |
meta-attribute-wrong |
| 无冲突重复 | meta-tag-conflicting-duplicates |
上线前清单
- 协议四件套 + description + site_name +
twitter:card出现在原始 HTML -
og:url与og:image为绝对 HTTPS;图片 200 可抓、无登录墙 -
og:url与rel=canonical一致 - 每个
og:*/twitter:*只有一个权威值 - OG 用
property=;Twitter card 用name= - description 是刻意写的文案,不是空字段
用公网 URL 扫一次
把线上页面贴进首页扫描。报告会按上面同一套规则 id 拆开——缺必填、相对路径图、错误属性、非法 Twitter card——而不是一张「看起来还行」的模拟卡。
第三方预览仍是近似。权威的抓取与缓存行为仍在各平台官方 debugger。先写对标签,再预览;若第一次抓取缓存了坏卡,再 rescrape。