跳到主要内容

HTTP缓存体系-强缓存-协商缓存

1. 两类缓存

类型含义是否发请求
强缓存直接用本地,不发请求否(Memory / Disk Cache)
协商缓存发请求验证,304 复用是(带条件头)

2. 强缓存

2.1 Cache-Control(HTTP/1.1,主流)

Cache-Control: public, max-age=31536000, immutable
指令含义
public任何缓存(浏览器、CDN)可存
private仅终端浏览器,CDN 不缓存
no-cache必须协商验证(不是不缓存)
no-store完全不缓存
max-age=N缓存 N 秒
s-maxage=NCDN 缓存 N 秒(覆盖 max-age)
immutable资源不会变,浏览器不发 if-modified
stale-while-revalidate=N过期 N 秒内用旧的,后台更新
must-revalidate过期必须验证

2.2 Expires(HTTP/1.0,老)

Expires: Thu, 18 Jun 2027 10:00:00 GMT

绝对时间,依赖客户端时钟。Cache-Control 优先级更高。

3. 协商缓存

强缓存过期后发请求带条件头,服务端可以返回 304 Not Modified(不带 body)。

3.1 ETag(推荐)

# 第一次响应
ETag: "abc123"
Cache-Control: no-cache

# 第二次请求
If-None-Match: "abc123"

# 服务端比对:相同则 304,不同则 200 + 新内容

ETag 是文件指纹(hash),精度高。

3.2 Last-Modified

# 响应
Last-Modified: Wed, 17 Jun 2026 10:00:00 GMT

# 请求
If-Modified-Since: Wed, 17 Jun 2026 10:00:00 GMT

时间戳,精度只到秒,文件 1 秒内多次改不准。

ETag 优先级 > Last-Modified。Nginx 默认两者都开启。

4. 决策树

资源是 hash 文件名(app.abc123.js)?
├── 是 → Cache-Control: public, max-age=31536000, immutable
└── 否
├── HTML(入口) → Cache-Control: no-cache
├── 用户私有数据 → Cache-Control: private, no-store
├── 公开 API → Cache-Control: public, max-age=60, s-maxage=600
└── 一般静态 → Cache-Control: public, max-age=3600

5. 实战配置

5.1 Nginx

# JS / CSS / 字体 / 图片 (hash 文件名)
location ~* \.(js|css|woff2?|png|jpg|svg|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

# HTML
location ~ \.html$ {
add_header Cache-Control "no-cache";
}

# API
location /api/ {
add_header Cache-Control "no-store";
proxy_pass http://backend;
}

5.2 Vercel / Netlify

vercel.json

{
"headers": [
{
"source": "/(.*\\.(?:js|css|woff2|png|jpg|svg))",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
},
{
"source": "/(.*\\.html)",
"headers": [
{ "key": "Cache-Control", "value": "no-cache" }
]
}
]
}

5.3 Express / Next.js

// 自定义中间件
app.use((req, res, next) => {
if (req.path.match(/\.(js|css|woff2)$/)) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
}
next()
})

Next.js 默认对 _next/static/ 加 immutable。

6. CDN 与浏览器缓存协作

Cache-Control: public, max-age=60, s-maxage=3600
  • 浏览器缓存 60s
  • CDN 缓存 1h
  • 用户高频访问走浏览器缓存
  • 跨用户走 CDN
  • 1h 后 CDN 回源

7. 缓存失效场景

7.1 hash 文件名 + immutable(推荐)

app.abc123.js (永久缓存)
app.def456.js (新版,新 hash)
index.html (no-cache,每次验证)

发版只刷 HTML,JS 自然分流。

7.2 版本 query

<script src="/app.js?v=20260618"></script>

简单但部分 CDN 默认不区分 query,要配置缓存键含 query。

7.3 主动 purge

CDN API 刷新指定 URL。配额有限,不要日常依赖。

8. Service Worker 缓存

详见专门一篇。SW 缓存优先级最高,可拦截所有请求。SW 注册后浏览器 HTTP 缓存对它请求无效,要在 SW 内部实现缓存策略。

9. 故障排查

# 看响应头
curl -I https://example.com/app.js

# 第二次请求带条件头
curl -I -H "If-None-Match: \"abc123\"" https://example.com/app.js
# 返回 304 Not Modified

# Chrome DevTools
# Network → 单击请求 → Headers + Response 看 Cache-Control / ETag
# Network → "Disable cache" 关闭缓存调试
# Application → Storage → Clear site data 清缓存

9.1 用户看不到新版本

  • 浏览器强缓存还在 → HTML 不应该长缓存
  • CDN 没刷新
  • Service Worker 缓存了旧版本

9.2 缓存策略没生效

  • add_header 在 location 多层会被覆盖(Nginx)
  • 中间代理改了头
  • Cookie / Authorization 头默认让响应不缓存

10. 常见反模式

  • HTML max-age 1 小时:发版 1 小时内用户拿不到
  • JS 不带 hash + 长缓存:发版用户老 JS + 新 HTML 不兼容
  • no-cache 当不缓存:实际是协商缓存。要 no-store
  • CDN 规则覆盖源站头:源站 immutable 被 CDN 改成 1 小时
  • 登录态 API 长缓存:用户互相看到对方数据
  • CORS 带 cookie 不加 Vary: Origin:第一个用户响应缓存给所有人
  • 不用 immutable:浏览器仍发条件请求,浪费 RTT

11. 延伸阅读