当前位置:首页 > 文章列表 > 文章 > 前端 > 前端页面发布后旧 JS 还在缓存:Cache-Control、文件指纹与回滚检查

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

来源:17golang原创 2026-08-09 04:49:07 0浏览 收藏

页面发版上线之后,部分用户刷新出来的还是旧版按钮逻辑,甚至同一个页面链接在不同设备上表现出两套完全不一样的行为。这个问题绝大多数情况都不是构建产物没上传成功,而是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删除
Java CompletableFuture 异常处理怎么选:exceptionally、handle 与 whenComplete 的流水线边界Java CompletableFuture 异常处理怎么选:exceptionally、handle 与 whenComplete 的流水线边界
上一篇
Java CompletableFuture 异常处理怎么选:exceptionally、handle 与 whenComplete 的流水线边界
Go json.Decoder 为什么第二次 Decode 会得到 EOF:请求体读取、空白与流式边界
下一篇
Go json.Decoder 为什么第二次 Decode 会得到 EOF:请求体读取、空白与流式边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    187次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    243次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    200次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    182次使用
  • CMMLU中文大模型评估基准:功能、使用教程与应用场景解析
    CMMLU
    深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
    171次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码