事情本身不复杂,线索都在日志里。前端框架在 hydration 之后才往 head 里插入 canonical,服务端渲染的 HTML 里没有这个标签。蜘蛛抓到的原始 HTML 里 canonical 缺失,带参数的地址被当成独立页面收录。

第一反应是「预渲染服务只缓存了骨架 HTML,没有等脚本执行完」,验证下来不成立。

真正麻烦的地方在于,几个原因的现象几乎一样。canonical 由客户端脚本拼装,依赖运行时数据。少数情况下是SSR 模板里没有预置占位标签。

另外,hydration 之后脚本又重写了一次 head 把服务端输出覆盖。

先看原始 HTML 里有没有 canonical

curl -s https://www.example.com/p/1?utm_source=x | grep -c 'rel="canonical"'

随后把 canonical 挪到服务端模板里输出

res.locals.canonical = 'https://www.example.com/p/1';

再往下确认带参数访问时输出稳定

curl -s 'https://www.example.com/p/1?utm_source=x' | grep -o '<link rel="canonical"[^>]*>'

两周后看参数页是否还被收录

site:www.example.com inurl:utm_source

怎么算真的好了,看的是同一批现象。canonical 改到服务端输出后,原始 HTML 里就能读到,三周内带 utm 的地址从索引里消失,规范地址统一。

任何影响收录的标签都不要交给客户端脚本,蜘蛛执行 JS 有延迟也有配额,服务端能输出的就别放到运行时。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189