你在产品页里加了 generateMetadata,让标题和 Open Graph 图片从 CMS 动态取,而不是写死一个字符串。本地跑通,Chrome 里点开链接,标签页标题正确,页面看着没问题,上线。
两天后,有人把链接贴进 Slack,展开的卡片显示的是站点默认标题,图片也没有——像是 generateMetadata 根本没跑过。你用 curl 请求同一个 URL 调试,返回的第一段 HTML 里确实是默认的 </strong>,不是产品名。函数没坏,你只是撞上了流式元数据(streaming metadata)。</p>
![]()
这是 Next.js 在「怎么回答浏览器」和「怎么回答其他所有客户端」之间,一个真实存在的行为差异。自 Next.js 15.2 起,它就是稳定默认行为。
两种客户端,两份契约
在流式元数据出现之前,规则简单但代价高:generateMetadata 完全解析之前,Next.js 不会发送任何 HTML。一次 2 秒的 CMS 调用,意味着每个访客、每次访问都要面对 2 秒白屏——框架没法在不知道 里放什么的情况下先把页面显示出来。
15.2 改变了面向普通访客的契约。对于动态渲染的路由,初始 HTML 可以在 generateMetadata 解析完成之前就流式发给浏览器,需要的地方先用占位值或此前已解析的值,真正的标签在 Promise 落定后稍后到达。真人用户不必等你的 CMS 调用就能看到页面。
但一个在首次绘制之后才到达的 ,对任何不执行 JavaScript、不重新读取 DOM 的东西来说都是不可见的:链接展开机器人、RSS 阅读器、一次快速的 curl、大多数 SEO 爬虫。对这些客户端而言,「标签最终会到」不够用——它们只读一次就走。</p>
所以 Next.js 通过 User-Agent 对照一份内置且可配置的名单来识别它们,这份名单叫 htmlLimitedBots。对名单上的客户端,它退回旧的阻塞式契约:等 generateMetadata 完成,再发送一份完整的文档。
Slack 那个 bug 的完整解释
Slack 的展开器匹配到了机器人名单,拿到的是诚实、完整、但慢的响应。你自己的 curl 测试没有可识别的机器人 User-Agent,拿到的是快速、流式的响应,并且恰好赶在真正的标签到达之前读到了内容。什么都没坏,两个不同的客户端被服务了两份不同但同样是有意设计的契约。
心智模型是这样的:generateMetadata 的运行方式始终一样——同样的代码、同样的参数、同样的父级元数据——但 Next.js 会根据「谁在问」来包装它的结果。两份契约都不是对方的 bug 版本,它们对不同受众都是正确的。错误在于假设只有一份契约。
拿一个元数据请求耗时 2 秒的路由,在真实浏览器里打开并录制 document 加载。你会看到初始 HTML 响应几乎立刻到达,其中包含:
- 同步解析出来的内容,或来自父级布局的内容
- meta charset 等基础标签
- 没有为待定标签预留任何位置——它们稍后到达,追加在 之后
片刻之后,getProductFromCMS 解析完成,Next.js 通过同一条连接把真正的 和 openGraph 标签流式发过来,追加在 末尾附近。因为这些是 React 会自动提升(hoist)的标签,浏览器在它们到达时会把它们重新安置到文档真正的 里。
页面稳定后在 DevTools 里看渲染后的 DOM,一切完全正常:一个完整、正确的 。但看原始网络响应,或者用读取字节而非执行脚本的工具去看,你期望的标签根本还不在那里——而当它们真的到达时,是追加在 body 内容之后,而不是拼接进最初的 块。
关键概念:流式元数据不会让 generateMetadata 跑得更快——它让页面不再等它。那次 fetch 仍然要花 2 秒;改变的是谁被迫坐等这 2 秒。
现在用一份在默认机器人名单上的 User-Agent 去请求同一个 URL——Slackbot、Twitterbot、facebookexternalhit、Bingbot 是它开箱覆盖的那类名字,此外还有 Google 的非渲染爬虫,比如 AdsBot-Google 和 Mediapartners-Google(主线的 Googlebot 会执行 JavaScript 并读取完整 DOM,所以 Next.js 明确验证它能从流式契约里拿到正确结果——它不属于被阻塞的客户端):
curl -A "Slackbot" https://example.com/products/wireless-mouse
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.