先给结论:静态响应与脚本渲染结果不一致,通常不是“哪边错了”,而是两边检查的层次不同。静态响应看到的是服务器最初返回的 HTML 和状态码,脚本渲染看到的是浏览器执行 JavaScript 后形成的 DOM。定位差异时,应先用同一 URL 分别记录状态码、最终 URL、页面主体文本和链接集合,再判断差异来自服务端返回、客户端重写,还是检查工具本身的等待条件。只有把差异落到具体字段上,后续修复动作才有明确目标。
假设一个商品页在静态响应中返回 200,HTML 里包含指向旧分类的链接;用脚本渲染后,页面主体显示“已下架”,旧分类链接被移除。此时不能直接判定为死链,因为静态响应里的链接可能只是模板占位,脚本执行后才决定是否保留。反过来,如果静态响应返回 404,脚本渲染后却显示正常内容,也要先怀疑检查工具是否在等待重定向或前端路由接管。
可区分的第一组证据是状态码与最终 URL 是否一致。若静态请求返回 301 并跳到新地址,而脚本渲染停在原地址,差异在重定向处理;若两者最终 URL 相同但链接集合不同,差异更可能在客户端渲染逻辑。
第一种解释是服务端对同一路径返回了不同内容。常见条件包括:请求头不同、Cookie 或登录态不同、地域或语言参数不同、缓存命中不同。比如静态检查未带 Cookie,拿到的是公开缓存页;脚本渲染带了登录态,页面渲染出用户专属导航,其中包含不同的内链。此时差异的根因在服务端内容协商或缓存策略,不在死链本身。
第二种解释是服务端返回同一份 HTML,但脚本执行后改写了 DOM。常见动作包括:前端路由替换视图、懒加载插入链接、根据接口结果移除失效入口、A/B 测试替换模块。此时静态响应里的链接可能从未被用户点击到,也可能在脚本失败时暴露给用户。要区分这两种解释,需要看脚本执行前后链接集合的差集,而不是只看页面是否“看起来正常”。
可以按下面顺序做一次小规模复核,假设只取 5 个代表性 URL,不追求覆盖全站:
<a href> 集合。如果差异链接自身返回 404 或 410,且只在静态响应中出现,说明它更可能是服务端模板残留;如果差异链接自身返回 200,但脚本渲染后不再出现,说明它更可能是客户端条件渲染。这个动作的结果会直接决定下一步:前者应回到模板或数据源清理,后者应检查前端路由和接口契约。
脚本渲染结果还受等待策略影响。若工具在 JavaScript 完成前就抓取 DOM,会得到接近静态响应的结果;若等待过久,又可能抓到延迟插入的推荐模块。两种等待条件都不算错,但必须和业务目标对齐:如果关心用户能否点到主导航,就应等主导航稳定;如果关心旧链接是否仍暴露,就应记录脚本执行前后的完整差集。
另一个容易误判的点是软 404。页面返回 200,但主体文本是“内容不存在”,脚本渲染后可能又插入推荐链接。此时状态码不能单独证明页面有效,链接集合也不能单独证明死链存在。应把状态码、主体文本和链接目标三者放在一起看,并说明各自适用的判断条件。
当差异定位到具体链接后,交接时不要只写“静态和渲染不一样”。应给出:URL、两种检查方式下的状态码、最终 URL、差异链接的绝对地址、该链接自身的响应状态、以及它出现在静态还是渲染结果中。若差异来自登录态或缓存,还要注明请求条件。这样开发或运维才能判断是改模板、改接口,还是调整检查脚本的等待条件。
需要提醒的是,robots.txt 的抓取限制不等于可靠的索引移除,站点地图也不保证收录;这些手段不能替代对具体 URL 的响应检查。若差异链接指向站外,还应分别核查目标站点的现行状态,而不是用一次请求结果推断长期有效。最终,网站死链检查的价值不在于得到一个“全站正常”的结论,而在于把静态与渲染之间的每个差异都落到可复现的条件和可执行的动作上。