报错是这么冒出来的。Next.js 15 的文章页在 generateMetadata 里请求内容接口取标题,接口超时设的 800ms,异常分支返回「文章详情 - 站点名」。上线一周后索引里八百多篇文章标题完全一样,GSC 提示重复标题。

中间走过弯路,把「页面渲染和 metadata 各请求一次接口,压力翻倍」当成主因,改完现象没变。

这类报错背后常见的原因有几处,逐个排除就行。metadata 生成时同步调用外部接口,接口一慢就走到兜底分支。另一种情况是异常处理只写了 return,没有重试也没有降级取本地数据。

不容易想到的是,构建期预渲染时接口不可用,静态产物里写的全是默认标题。

动手时先在日志里搜兜底标题出现的次数

grep -c '文章详情' /var/log/app/next.log

随后改成从本地数据或缓存里取标题,不在 metadata 里发外部请求

const post = await getPostCached(params.id);

给接口调用加上超时后的重试与本地兜底

const title = post?.title ?? fallbackTitle;

末了重新构建后抽十条抓一遍标题

curl -s https://www.example.com/p/1024 | grep -o '<title>[^<]*'

恢复的判定其实很直接。线上页面的标题恢复成各自的文章标题,重复标题提示从八百条降到个位数。

补一句。metadata 里做远程请求等于把页面能不能被正确索引交给了另一个服务的响应时间,能预取就别实时取。监控比日志更早给出信号,事后回看经常能提前半小时发现异常。

作者 9823189