事情本身不复杂,线索都在日志里。前端框架在 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 有延迟也有配额,服务端能输出的就别放到运行时。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。
