你把文章 URL 粘进 X(原 Twitter)的推文输入框,预期看到带大图的卡片,结果要么是干巴巴的链接,要么只有标题没图,要么显示的还是两周前已经换掉的旧图。同样的 URL 在 LinkedIn、Slack 都好好的。这几乎从来不是「X 又坏了」——通常是四件具体的事:缺 twitter:card 声明、og:image / twitter:image 抓取不到、图本身不符合 X 的尺寸/格式要求,或者 X 那边 7 天的卡片缓存还没过期。2026 年的现实是:X 已经把 Open Graph 当 fallback 在读,所以对大多数站点你不需要单独的 twitter:image——但你至少要有 twitter:card。
5 秒判断你是哪种情况
- 卡片有标题和描述但没图 →
twitter:image/og:image抓不到或图太小 - 完全没有卡片,只是裸链接 → 整个
twitter:cardmeta 缺失,或者content值不被识别 - 换了图后还是显示旧图 → X 卡片缓存
- Facebook / LinkedIn 都正常,只有 X 不行 →
twitter:image用了相对路径,或twitter:card缺失 - 新 URL 都正常,只有某一条不行 → 这条 URL 的图返回非 200(签名 URL 过期、文件已删)
常见原因(按命中率排序)
1. 缺 twitter:card 声明
没有 <meta name="twitter:card">,X 根本不知道要渲染成哪种形状的卡片,结果要么回退到小型 summary,要么完全不渲染。这是「首次分享没卡片」最常见的原因。
怎么判断:view-source 搜 twitter:card。没有就是它。
修复:至少输出
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="文章标题" />
<meta name="twitter:description" content="一句话摘要" />
<meta name="twitter:image" content="https://yourdomain.com/og/article.png" />
summary_large_image 是大多数内容站想要的 1.91:1 宽卡片;summary 是带小方图的紧凑卡。
2. twitter:image 用了相对路径
X 爬虫不会解析相对 URL。content="/og/foo.png" 直接被当字符串拿去 fetch,自然失败。
<!-- 错:相对路径 -->
<meta name="twitter:image" content="/og/article.png" />
<!-- 对:绝对 https URL -->
<meta name="twitter:image" content="https://yourdomain.com/og/article.png" />
怎么判断:view-source 看 content 属性,/ 或 ./ 开头都是错的。
3. 图不符合 X 的尺寸/格式要求
X 对 summary_large_image 有硬性要求:
| 字段 | 规则 |
|---|---|
| 格式 | PNG / JPEG / WebP(动图 GIF 可,但只取第一帧) |
| 最小尺寸 | 300 x 157 px |
| 最大尺寸 | 4096 x 4096 px |
| 最大文件大小 | 5 MB |
| 宽高比 | 1.91:1(偏离会被居中裁切) |
| 协议 | 只接受 HTTPS |
200x200 的 favicon、6MB 的大图、http:// URL 都会静默失败——卡片可能还会渲染,但图位是空的。
怎么判断:
curl -A "Twitterbot/1.0" -I "https://yourdomain.com/og/article.png"
# 期望 HTTP/2 200, content-type: image/png, content-length 小于 5000000
identify https://yourdomain.com/og/article.png # ImageMagick 确认尺寸
content-length 超 5,000,000 或尺寸不到 300x157 就重新导出。
4. X 缓存了旧版本(7 天缓存)
X 按 URL 缓存卡片数据最长 7 天。你改完 meta 重发上线,但推文里还是旧图。2023 年起 cards-dev.twitter.com/validator 已被关闭,没有公开的「强制重抓」按钮。
怎么判断:URL 粘到 opengraph.dev 或 cardvalidator.io——它们会即时抓取,显示当前 meta 实际输出的卡片。若验证器显示新图但 X 还在显示旧图,就是 X 缓存。
修复(按有效性排序):
- URL 加个 cache-buster 参数:推
https://yourdomain.com/article/foo?v=2而不是裸 URL。 - 等 7 天,缓存自然过期。
- 改 canonical URL(极少用,除非 slug 真换了)。
5. 图在 auth / CDN 签名 / 防盗链后
twitter:image 指向 Cloudinary 签名 URL、需要 Referer 的 CDN,或登录墙后的路径。Twitterbot 拿到的是 401 / 403 / 302。
curl -A "Twitterbot/1.0" -I "https://cdn.yourdomain.com/og/article.png"
# 200 + image/png 表示 OK
# 401 / 403 / 302 表示被挡
修复:把 OG 图放进静态 public/ 目录,或在 CDN 上白名单 Twitterbot/1.0 user-agent。
把 Open Graph 当 fallback(2026 年的实际行为)
2026 年 X 已经在缺 twitter: 命名空间时回退读 Open Graph。如果 og:image 设得对,你只要再加一行 <meta name="twitter:card" content="summary_large_image" /> 就够,可以省掉 twitter:image。2026 年内容站的推荐最小集:
<meta property="og:title" content="文章标题" />
<meta property="og:description" content="一句话摘要" />
<meta property="og:image" content="https://yourdomain.com/og/article.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://yourdomain.com/articles/article-slug/" />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary_large_image" />
只有在你确实想让 X 用一张和 Facebook/LinkedIn 不一样的图时,才单独设 twitter:image。
最短修复路径
按命中率排序:
- 加
twitter:card(如果缺) → 修掉 ~45% 的「没卡片」情形 - 把
twitter:image(或og:image)改成绝对 https → 修掉 ~25% - 图重新导出成 1200x630 PNG/JPEG,文件 < 1MB → 修掉 ~15%
?v=N强刷,或等 7 天 → 修掉 ~10%- CDN 白名单 Twitterbot / 把图移进 public/ → 最难的 5%
预防建议
- 统一用 1200x630 PNG 或 JPEG,目标 < 500KB,从主域名(不要签名 CDN)出。
- 用一个 helper 强制
og:image/twitter:image一定是带协议的绝对 URL。 - CI 加检查:每篇新文章的 OG 图
curl -A "Twitterbot/1.0" -I确认 200、content-type 正确、content-length 小于 5MB。 - 除非真要分平台用不同图,否则别设
twitter:image——让 X 回退读og:image,少一个字段就少一处会漂移的地方。 - OG 图按标题自动生成(
@vercel/og、satori、puppeteer),从源头杜绝「忘加图」。
FAQ
Q:Twitter 老的卡片验证器死了,现在怎么测? A:用 opengraph.dev 或 cardvalidator.io,都会即时抓你的 URL 并模拟 X 的渲染。要看绝对真相,用一个小号发一次然后截图。
Q:改完 meta 还是显示旧图,X 多久会重抓?
A:X 卡片缓存是 7 天,没有公开的强制刷新。最快的 workaround 是推的 URL 加 ?v=2——X 会当成新 URL 重抓。
Q:og:image 和 twitter:image 都要写吗?
A:不必。2026 年 X 把 og:image 当 fallback 在读。除非要分平台用不同图,否则只写一个就够。唯一必须的是 <meta name="twitter:card">。
Q:卡片有图但裁切得很怪。
A:summary_large_image 强制 1.91:1。源图是 1:1 或 4:3 会被居中裁切。重新导出成精确的 1200x630。
Q:动图 GIF 只显示第一帧? A:是的,X 会拿第一帧定格。如果第一帧是黑底标题卡,卡片就像没图。直接用静态 PNG/JPEG。
Q:私信里 unfurl 正常,推文里不行。 A:私信预览是另一套即时抓取的管线,不走 7 天卡片缓存;推文渲染走卡片管线。两边表现不一致,就确认是缓存问题不是 meta 问题。