JavaScript 与 CSS 缓存更新:文件指纹、版本号和安全发布
缓存更新通常通过给发生变化的 JavaScript 或 CSS 分配新 URL 实现,例如在文件名中加入内容哈希。版本化文件可以使用较长缓存寿命,而 HTML 应通过较短寿命或重新验证及时发现新版本。
本指南内容
如果新版本在隐私窗口中正常,回访用户看到的却还是旧界面,先比较资源 URL,不要立刻修改整个网站的缓存设置。部署可能已经替换服务器文件,但浏览器仍然保有相同 URL 对应的有效缓存响应。理解是哪一个地址继续复用,比笼统地说“缓存有问题”更有帮助。
可靠发布应明确 HTML 与资源的版本关系。下例让旧版和新版文件同时保留,使部署前已经打开的页面仍能取得匹配资源。例中的哈希只是说明性文件名,并非任何真实文件计算出的值。
把 HTML 文档和带版本的资源分开规划
可以把 HTML 看成指向某个发布版本的小型清单。版本 A 引用 app.4c21.js 与 site.19ab.css。版本 B 修改应用后引用 app.9d80.js;如果样式文件的字节未变,可以继续引用 site.19ab.css。这样,资源身份对应其内容,而不是仅对应一个长期不变的文件名。
未变化的资源因而可以复用,变化后的资源则拥有新的缓存身份。如果 HTML 本身被长时间缓存,访客仍会读到旧文档,也就继续请求旧资源。评估更新延迟时,应先问页面从哪里发现新文件,而不是只检查脚本是否已经上传。
| 资源 | 版本 A | 版本 B | 预期处理 |
|---|---|---|---|
| HTML | 引用 app.4c21.js | 引用 app.9d80.js | 重新验证,或使用经过设计的短缓存寿命 |
| 应用脚本 | app.4c21.js | app.9d80.js | 字节变化时分配新 URL |
| 未变化的样式 | site.19ab.css | site.19ab.css | 复用相同内容地址 |
| 旧版资源 | 保持可用 | 继续为旧页面保留 | 在所需兼容期内保留 |
缓存指令要与资源约定匹配
对于公开、以内容版本命名且该地址下永不修改的文件,较长的 max-age 加 immutable 可能合适。HTML 使用 no-cache 时仍允许存储,但再次复用前需要验证;no-store 则要求不要存储响应。这些含义不同,不能因为名字中都有“缓存”就混用,也不应把一个响应头复制到全站所有类型的文件上。
下面只是策略示例,不是所有主机都通用的配置。检查托管平台实际发出的响应头,以及边缘缓存的单独设置。本地配置文件中出现某个值,并不能证明公开地址上的浏览器和 CDN 收到了同样的策略;发布后必须重新观察真实响应。
Versioned public asset: /assets/app.9d80.js
Cache-Control: public, max-age=31536000, immutable
HTML document: /index.html
Cache-Control: no-cache
The URL /assets/app.9d80.js must continue to serve the same bytes.查询版本号有效的前提是缓存键包含它
例如 /app.js?v=release-b 可以与 /app.js?v=release-a 拥有不同缓存身份,但应核查每一层相关缓存是否把该参数纳入缓存键。如果 CDN 规则忽略查询字符串,两种地址可能命中同一个缓存对象,版本参数就失去了预期作用。验证时同时记录完整请求地址与实际返回字节。
不要每次页面加载都生成随机版本值。所有访客都请求独一无二的地址,会破坏有用的缓存复用。内容哈希或稳定发布版本只在资源改变时更新。片段如 /app.js#release-b 不会随 HTTP 请求发送,因此不能用来区分服务器上的资源版本。
定位仍在提供旧字节的具体层
对受影响访客,把 HTML 响应、其中引用的资源地址与资源实际字节连起来检查。文档仍引用旧哈希时,调查 HTML 新鲜度;文档引用新哈希却取得旧内容时,调查部署、缓存键和源站响应。网络响应正确但行为仍旧时,还需检查已运行的页面状态,以及是否有服务工作线程控制访问。
清除 CDN 缓存不会自动删除所有访客浏览器中仍然有效的响应。反过来,浏览器强制刷新也只是本地诊断,不会替所有人修复发布流程。每次测试都记录绕过了哪一层缓存,才能解释为什么某个测试成功,以及普通回访是否会得到同样结果。
如果某个外部脚本的地址和响应策略由第三方控制,应把它与自己部署的资源分开排查。自己站点的哈希文件正常,并不能证明外部依赖已经更新。不要随意给供应商地址添加参数来掩盖问题,应先确认供应商是否支持该参数,以及返回资源是否仍符合其集成要求。
- 记录最终 HTML 地址及其引用的脚本或样式地址。
- 检查状态码、Content-Type、Cache-Control、ETag 和可用的缓存状态头。
- 确认目标版本返回预期文件,而不是 HTML 回退页面。
- 应用安装服务工作线程时,单独检查它的缓存规则。
- 既测试全新访问,也测试跨越发布时间仍然打开的页面。
部署和回滚期间,让旧页面继续工作
先上传新资源,再发布引用它们的 HTML,或者使用能够整体切换完整版本的平台部署方式。为旧 HTML 与已打开页面保留之前的资源。访客可能在首次打开页面很久以后才触发某个动态模块,不能仅因为首页已更新,就认为旧资源没有使用者。
例如发布 B 时同时保留 A、B 资源,验证 B 正常,再保留把 HTML 指回 A 的能力。立刻删除 app.4c21.js,可能破坏仍持有 A 文档的访问;用新字节覆盖相同哈希地址,同样违背内容寻址约定,也可能让回滚结果不可预测。
回滚清单应包含文档和依赖资源的配对关系。仅把一个 HTML 文件还原,却没有它需要的资源,并不算完整回滚。评审时可从旧文档逐个检查依赖地址是否仍然存在,并确认回退路由没有把缺失的脚本伪装成成功返回的页面。
先保证正确性,再测量缓存复用效果
执行可复现的回访测试:首次访问下载资源,再次访问复用允许复用的内容,新发布后只获取发生变化的版本。检查实际网络传输,包括重新验证请求,不要把所有缓存相关响应都视为同一种情况。记录测试环境是否禁用了缓存,否则开发者工具设置可能改变观察结果。
缓存节省计算器可根据自己输入的命中率假设估算源站请求减少量,传输计算器可根据请求数和响应大小估算字节。它们不能替代公开响应头与实际命中率测量,应在版本更新已经正确工作的前提下用于比较计划。
缓存优化报告中应区分浏览器复用、边缘命中和源站返回。只有明确测量层,才能知道减少的是用户传输、边缘到源站的请求,还是源站计算。不要把本地强制刷新的结果直接当作普通用户的长期体验;保留常规访问路径的记录,才能评估真实收益。
常见问题
旧 CSS 没更新,是否应该关闭所有缓存?
通常应先找到陈旧文档或资源,再建立稳定版本关系。全站关闭复用可能增加传输与延迟,却仍然没有解决发布流程不明确的问题。
修改 ETag 会立即替换仍然新鲜的缓存文件吗?
浏览器在响应仍然有效时,可能不联系服务器就直接复用。因此,内容变化后使用新 URL,可以避免依赖未来某次重新验证才发现更新。
部署后可以删除旧哈希文件吗?
只有在满足缓存 HTML、已打开页面和回滚所需的兼容期以后才适合删除。只要受支持的文档还可能引用它,就应保留对应资源。
来源与定义
方法核验日期: