浏览器缓存实战:强缓存、协商缓存,以及发版后用户还是旧页面的五个原因
## 一、两条缓存路径
浏览器请求资源时先查本地缓存,分两种命中方式:
- **强缓存**:直接读本地,**不发请求**,状态码 200(from disk cache / memory cache)
- **协商缓存**:发请求到服务端问"这个还能用吗",能用回 **304 Not Modified**,只回响应头不带 body
两者的区别就是"要不要问服务端"。强缓存快但更新滞后,协商缓存每次都有一次往返但内容永远最新——所以最佳实践是**组合使用**:静态资源用强缓存 + 文件名 hash 解决更新,HTML 用协商缓存保证入口最新。
## 二、强缓存的两个头
```http
Cache-Control: max-age=31536000, immutable // 优先级高
Expires: Wed, 21 Oct 2026 07:28:00 GMT // HTTP/1.0 老写法,受本地时间影响
```
`Cache-Control` 优先于 `Expires`,现代项目只写前者即可。几个常用指令:
| 指令 | 含义 |
| --- | --- |
| max-age=N | 相对过期时间(秒) |
| no-cache | 不用强缓存,但可以走协商缓存(**不是不缓存**) |
| no-store | 完全不缓存,连协商都不走 |
| immutable | 有效期内绝不发请求,连刷新都不验证 |
最容易搞混的是 `no-cache` 和 `no-store`:前者意思是"每次都去服务端验证",后者才是"彻底别存"。
## 三、协商缓存的两组头
```http
ETag: "5f8a-1b2c" // 服务端响应
If-None-Match: "5f8a-1b2c" // 浏览器下次请求带上
```
```http
Last-Modified: Tue, 20 Oct 2026 06:00:00 GMT
If-Modified-Since: Tue, 20 Oct 2026 06:00:00 GMT
```
优先级:**ETag / If-None-Match 高于 Last-Modified / If-Modified-Since**,同时存在时以前者为准。
为什么 ETag 更可靠?因为 Last-Modified 只精确到秒,一秒内多次修改感知不到;而且它依赖文件 mtime,内容没变但 touch 了文件也会被判定为已修改。ETag 是内容指纹,更准确,代价是服务端要多算一次。
## 四、完整判断链条
1. 有强缓存且未过期 → 直接用,**不发请求**
2. 强缓存过期或用了 no-cache → 发请求,带上 If-None-Match / If-Modified-Since
3. 服务端比对 → 没变回 304(浏览器用本地副本),变了回 200 带新内容
4. 用户按 F5:跳过强缓存,走协商缓存;Ctrl + F5:全部跳过,强制拉新
## 五、Nginx 标准配置
```nginx
# HTML 入口:协商缓存,保证发版后立刻拿到新引用
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# 带 hash 的静态资源:一年强缓存 + immutable
location ~* \.(js|css|png|jpg|svg|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
```
核心前提是**文件名带内容 hash**(webpack/vite 默认就是这么做的)。文件内容一变 hash 就变,URL 变了自然不受旧缓存影响——这才是"长缓存 + 快速更新"能同时成立的原因。
## 六、发版后用户还是旧页面:五个原因
### 1. HTML 被强缓存了
最常见。入口 HTML 配了 max-age,用户拿到的还是旧 HTML,里面引用的还是旧 hash 的资源。**修复:HTML 一律 no-cache**。
### 2. index.html 有缓存但 JS 没更新(或反之)
典型的版本错配:新 HTML 引用 `app.a1b2.js`,但该文件因为 CDN 缓存或发布不完整返回 404/旧内容,页面白屏。排查看 Network 里失败的资源。
### 3. CDN 边缘节点没刷新
源站已更新,CDN 还在缓存旧文件。**修复:发布流程里加 CDN 刷新(目录级 purge),并确认刷新完成再放量**。
### 4. Service Worker 拦截了请求
旧 SW 缓存优先返回,页面永远拿不到新资源。**修复:更新 SW 版本号并在 activate 里清旧 cache;必要时引导用户跳过等待**。
### 5. 用户侧本地缓存顽固
浏览器磁盘缓存、代理缓存、企业网关缓存都可能。**验证方法**:无痕窗口打开、加随机 query 参数、看响应头 `age` 和 `x-cache`。
## 七、排查顺序(照这个走最快)
1. 无痕窗口打开——还旧就是服务端/CDN 问题,新了就是用户本地缓存
2. 看 HTML 响应头有没有 `Cache-Control: no-cache`
3. 看 CDN 响应头 `x-cache: HIT/MISS` 与 `age`
4. 看资源 URL 的 hash 是否变了
5. 最后才怀疑 SW 与浏览器缓存
## 八、总结
缓存策略一句话:**HTML 不缓存或协商缓存,静态资源长强缓存 + hash 文件名**。发版问题先分清"是入口旧还是资源旧",再按 HTML → CDN → SW → 本地 的顺序排查,比盲目让用户清缓存高效得多。
- 点赞
- 收藏
- 关注作者
评论(0)