登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

前端页面发布后旧 JS 还在缓存:Cache-Control、文件指纹与回滚检查

来源:17golang原创

时间:2026-08-09 04:49:07 267浏览 收藏

页面发版上线之后,部分用户刷新出来的还是旧版按钮逻辑,甚至同一个页面链接在不同设备上表现出两套完全不一样的行为。这个问题绝大多数情况都不是构建产物没上传成功,而是HTML入口、带内容指纹的静态资源、CDN缓存还有Service Worker各自的缓存时长规则没对齐。把这几层缓存拆开捋清楚,旧JS残留的问题就不再是靠猜的玄学问题,而是一套可以一步步复查的标准发布校验动作。

要点速览

  • HTML入口适合配置短缓存或者协商缓存,带内容指纹的JS、CSS资源才适合开长缓存。
  • 发布验收先确认HTML里引用的资源指纹对不对,再查CDN响应头和浏览器实际加载结果。
  • 做版本回滚的时候要保留上一版的全部静态资源,不能只把HTML链接切回旧版本就完事。
  • 如果项目接入了Service Worker,还要额外检查注册状态、页面控制权和缓存命名空间。

先判断:旧页面来自哪一层缓存

排障时不用一上来就让用户清空浏览器缓存。先拿一台能复现问题的设备打开开发者工具,在Network面板里勾选Disable cache,这个选项仅对当前调试会话生效,重载页面后如果问题直接消失,说明发布链路里的某一层缓存还在返回旧内容;如果问题依旧复现,那就要先检查入口HTML本身是不是就引用了旧版资源。

可以先用脚本命令直接校验服务器上的入口文件:

curl -sSI https://example.com/app/ | sed -n '1,12p'
curl -s https://example.com/app/ | grep -Eo 'assets/[A-Za-z0-9._-]+\.js' | head

重点记录 cache-controlageetagx-cache 和HTML里写死的资源文件名。比如发布文件夹里已经生成了 app.91d4.js,入口却还在引用 app.6a20.js,这根本不是CDN刷新能解决的问题,是HTML发布环节或者构建产物映射逻辑出了错。

前端发布后的缓存分层:浏览器、CDN、HTML入口和带指纹JS之间的命中与更新路径

正确拆分缓存策略:入口短缓存,资源长缓存

HTML的作用就是告诉浏览器“现在你该加载哪一个版本的资源”,如果它本身被设置了超长缓存,用户就很可能一直拿不到新的资源指纹。因此入口文件通常设置较短的 max-age,同时保留 ETag 做协商缓存。文件名带内容指纹的JS、CSS、字体和图片资源完全可以开长缓存,因为只要资源内容有改动,构建工具就会自动生成全新的文件名。

# HTML:允许快速重新确认版本
Cache-Control: no-cache

# 带内容指纹的静态资源:版本变了就换文件名
Cache-Control: public, max-age=31536000, immutable

no-cache 不等于完全不缓存,它的含义是每次使用本地缓存前都要先向服务器确认资源是否更新;真正需要禁止浏览器本地存储内容的时候才要用到 no-store。配置全部写完之后,分别请求入口HTML和带指纹的静态资源,确认响应头没有被Nginx、对象存储或者CDN的默认全局规则覆盖掉。

发布步骤:先上传资源,再切换 HTML

一套更不容易出故障、方便快速回滚的发布顺序是:先上传全部新版静态资源,再发布引用新资源的HTML入口,最后按需刷新入口的CDN缓存。新资源先上传到位非常关键,因为CDN的边缘节点是分布在不同位置的,收到更新通知的时间不一样;如果HTML先切到新版,对应的新JS还没上传完成,中途访问的用户就会碰到白屏或者脚本404的问题。

  1. 构建项目得到 release-20260809 文件夹,检查输出的JS文件名是不是都带上了本次构建的新内容指纹。
  2. 把全部新资源上传到不可变的公开路径,确认所有静态文件返回200状态码和正确的 content-type
  3. 发布新版HTML,逐行检查里面的脚本引用确实指向本次发布的资源指纹。
  4. 只刷新HTML入口和必要的CDN别名配置,不要整站无差别清缓存,反而容易把正常资源搞失效。
  5. 分别用从未访问过站点的新浏览器、之前已经打开旧页面的浏览器各做一次验收。

如果项目接入了Service Worker,检查范围还要扩大到 navigator.serviceWorker.getRegistrations()、当前页面是否还被旧版worker控制,以及缓存名称是否从 app-cache-v12 升级到新版本。新worker安装成功,不代表已经打开的旧页面立刻就会交给它接管;强制触发接管之前要确认不会打断正在进行的文件上传、表单编辑或者支付流程。

验收信号:浏览器实际加载的版本要对得上

只看CDN控制台显示的“发布成功”完全不够。打开Network面板,筛选 JS,逐个确认脚本的请求URL、状态码、Age 和响应头配置;也可以在Console面板里打印构建时注入到代码里的版本号,比如 window.__RELEASE_ID__。线上应用还可以在页面角落加一个仅开发运维人员可见的发布版本标识,出问题的时候比反复猜测缓存原因效率高很多。

curl -s https://example.com/app/ \
  | grep -Eo 'assets/[^" ]+\.js' | head -1

curl -sSI https://example.com/app/assets/app.91d4.js \
  | grep -iE 'cache-control|age|etag|x-cache'

验收的时候至少覆盖三种场景:首次访问站点、强制刷新已经打开旧页面的标签页、切换到手机流量之后重新访问。如果只有之前打开过旧页面的标签页出问题,优先排查Service Worker的控制范围和后台运行的旧代码;如果所有新访客访问都异常,再回头检查HTML、CDN和静态资源的发布顺序。

前端发布验收与回滚:用资源指纹、响应头和页面版本号确认新旧版本,再安全切回上一版

回滚路径:保留资源,切换入口

最稳妥的回滚操作不是直接删掉新生成的发布文件夹,而是把入口HTML的引用切回上一版资源,同时保留新旧两套带内容指纹的静态资源。这样之前打开的旧标签页、CDN边缘节点缓存还有正在后台执行的旧代码,都还能找到它们引用的对应文件。回滚完成之后重新检查HTML里的资源指纹,同时确认页面报错率、脚本加载失败数和核心接口请求都恢复正常。

如果提前把旧版静态资源删掉了,就算把HTML切回旧版本,同样可能出现大面积白屏问题。静态资源文件夹至少要保留最近两次可用的发布版本,旧资源的清理操作要单独走定时保留策略,不要和发布版本切换的脚本绑在一起执行。

常见误区与复盘清单

  • 把站点所有文件都设置为一年超长缓存:入口HTML的版本被固定,用户自然拿不到新的资源指纹。
  • 只清CDN缓存不检查HTML内容:入口本身引用的资源链接是错的,清一百次缓存也修复不了文件映射关系。
  • 只测试无缓存的全新窗口:很多真实线上故障恰恰发生在已经打开了好几个小时的旧标签页里。
  • Service Worker更新之后立刻强制接管:很容易打断用户正在进行的业务操作。

每次发版之后把对应的HTML指纹、全量资源清单、响应头样本、CDN刷新范围和回滚点记录下来。下次再碰到“只有部分用户加载了旧版”的告警,直接对比两次发布的release ID就能定位问题,不用反复引导用户按Ctrl+F5清缓存。

相关问题

为什么文件名已经带指纹,还需要刷新 CDN?

新指纹对应的资源本身通常不需要刷新,但HTML入口可能还被CDN缓存着旧版本。需要更新的是入口文件或者对应的缓存规则,不是把所有带指纹的不可变资源一起清掉。

no-cache 会让页面完全不缓存吗?

不会。它允许浏览器本地存储响应内容,但每次使用前都要重新向服务器确认资源有没有更新。入口文件常用这个策略,能同时兼顾版本实时校验和协商缓存的性能收益。

Service Worker 怎样确认已经控制当前页面?

查看 navigator.serviceWorker.controller 是否正常返回,同时在开发者工具的Application面板里检查当前的registration状态、缓存名称和页面控制范围。新worker首次安装完成之后,当前打开的页面往往要等一次重载才会正式交给新版worker接管。

前端缓存适配的核心从来不是“把缓存全部关掉”,而是把版本边界做的清晰可见:HTML走快速校验策略,带指纹的静态资源长期复用,发布顺序先传资源再切入口,验收同时核对资源URL、响应头和页面上的release ID,回滚只切入口不删存量资源。把这几个校验点写进发布脚本和检查清单,旧JS残留的定位和恢复速度都会快很多。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>