缓存失效与部署策略
通过内容哈希文件名、部署顺序和 Cache-Control 搭配实现"长期缓存 + 即时更新"。
#tech / dev / frontend
#type / concept
#status / evergreen
#platform / browser
#tech / dev / deploy
[!info] related notes
- 所属 MOC: 浏览器缓存体系
- 前置概念: 强缓存
- 关联概念: CDN 边缘缓存, Service Worker 与 Cache API, 缓存不更新排查路线
缓存失效与部署策略
一句话定义
Cache busting 是在资源内容变化时改变其 URL,使缓存自动失效并获取新版本。核心模式是”长期缓存 + 内容哈希文件名”。
核心矛盾
- 想要:静态资源长期缓存,减少重复下载
- 需要:资源更新时用户立即拿到新版本
解决方案:URL 变了 → 缓存自动失效 → 配合长期 max-age 实现最佳性能。
策略一:内容哈希文件名(推荐)
app.3a7f2c1b.js
style.9e8d7c6a.css
logo.a1b2c3d4.png
构建工具配置
Webpack:
output: {
filename: '[name].[contenthash:8].js'
}
Vite:默认行为,无需配置。
工作原理
- 文件内容不变 → 哈希不变 → URL 不变 → 命中长期缓存
- 文件内容变了 → 哈希变了 → URL 变了 → 浏览器视为新资源
- 配合
Cache-Control: public, max-age=31536000, immutable实现最佳缓存
为什么有效
浏览器缓存键是 URL。URL 变了,即使路径相同,缓存也不会命中旧版本。
策略二:查询字符串(不推荐)
app.js?v=3a7f2c1b
问题
- 部分 CDN 和代理在缓存时忽略查询字符串
app.js?v=1和app.js?v=2可能被某些 CDN 视为同一资源- 不如文件名哈希可靠
策略三:版本路径
/v2/app.js
/api/v2/users
适用场景:API 版本控制。静态资源一般不用这种方式。
HTML 问题
index.html 的 URL 由用户访问路径决定,无法加哈希。
解决方案
# index.html
Cache-Control: no-cache
no-cache= 可以缓存,但每次使用前必须验证- 浏览器每次都带
If-None-Match/If-Modified-Since去问服务器 - 内容没变 → 304(几乎零开销)
- 内容变了 → 200 + 新 HTML → 新 HTML 引用新的哈希资源 URL
这就是”长期缓存 + 哈希”模式的完整闭环
index.html(no-cache,每次验证)
├── app.3a7f2c1b.js(max-age=1 年,immutable)
├── style.9e8d7c6a.css(max-age=1 年,immutable)
└── logo.a1b2c3d4.png(max-age=1 年,immutable)
发布新版本:
index.html(内容变了 → 304 变 200)
├── app.7f8e9d0c.js(新哈希 → 新 URL → 不命中旧缓存)
└── style.b2c3d4e5.css(新哈希 → 新 URL)
Cache-Control: immutable
Cache-Control: max-age=31536000, immutable
告诉浏览器:这个资源在整个有效期内绝对不会变。即使用户主动刷新(F5),浏览器也可以跳过重新验证。
浏览器支持
| 浏览器 | 支持 |
|---|---|
| Firefox | 支持 |
| Safari | 支持 |
| Chrome | 不支持(截至 2026),退化为普通 max-age 行为 |
Chrome 不支持时,退化为普通长期缓存,用户强刷仍会重新验证。文件名哈希保证了即使验证,拿到的也是正确版本。
部署顺序
正确顺序
-
先部署带哈希的资源(JS/CSS/图片)
- 它们是新 URL,旧缓存不会命中
- 即使被提前缓存也没问题,因为内容就是新的
-
再部署 HTML
- HTML 引用新的哈希 URL
- 用户拿到新 HTML 后,会请求新的资源 URL
错误顺序
如果先部署 HTML 再部署资源:
- 用户拿到新 HTML → 请求新哈希的 JS/CSS
- 新 JS/CSS 还没部署 → 404
灰度/蓝绿部署
在蓝绿部署中,旧版本和新版本同时存在:
- 旧 HTML 引用旧哈希的资源(仍在线)
- 新 HTML 引用新哈希的资源(刚部署)
- 两套资源可以共存,互不干扰
Service Worker 考虑
如果使用 Service Worker 做预缓存:
- SW 的 precache manifest 包含资源哈希
- 新版本 SW 需要与新资源原子更新
- 旧 SW 可能还在服务旧缓存 → 需要
skipWaiting()+clients.claim()策略 - 详见 → Service Worker 与 Cache API
常见误解
- “查询字符串
?v=hash和文件名哈希效果一样” → 不一样,部分 CDN 会忽略查询字符串 - “设置了
max-age=0就够了” → 这样每次都要验证,丧失了缓存性能优势 - “HTML 也要长期缓存” → 不行,HTML 是入口,必须能及时更新
- “部署新版本后需要让用户清缓存” → 如果正确使用了文件名哈希,完全不需要