两类缓存,一套指令
HTTP 缓存分两类:浏览器私有缓存与 CDN/Nginx 共享缓存,Cache-Control 的指令同时约束两者。配错方向只有两种结局:要么用户更新看不到,要么回源流量打爆源站。
先明确目标再动手:带内容指纹的静态资源追求「长强缓存」,HTML 与接口追求「每次校验新鲜度」——两种策略截然不同,必须分开配置。
# 响应头速查(下文逐个展开)
# Cache-Control: public, max-age=31536000, immutable 静态资源
# Cache-Control: no-cache HTML/接口(可缓存但需校验)
# Cache-Control: no-store 敏感数据,禁止缓存强缓存:Cache-Control 指令逐个讲清
`max-age=N` 秒数内直接用本地缓存、不发请求;`s-maxage` 只约束共享缓存且优先级更高;`public`/`private` 决定能否被 CDN 存;`immutable` 告诉浏览器资源永不变(指纹 URL 专用,连刷新都不校验)。
最容易误解的两个:`no-cache` 不是「不缓存」,而是「缓存前必须协商校验」;`no-store` 才是彻底不落缓存。Expires 是 HTTP/1.0 的绝对时间遗物,被 Cache-Control 覆盖,现代配置只写后者。
Cache-Control: public, max-age=31536000, immutable
# 指纹资源:一年强缓存 + 永不校验
Cache-Control: private, max-age=0, must-revalidate
# 私有内容:每次都校验协商缓存:ETag 优先于 Last-Modified
强缓存过期后进入协商:浏览器带 `If-None-Match: <ETag>`(或 `If-Modified-Since`)问服务器「变了吗」,没变回 304(无响应体,几乎零流量),变了回 200 + 新内容。
优先 ETag:它是内容级标识,而 Last-Modified 只精确到秒、且修改时间变了内容未必变。Nginx 默认给静态文件自动生成 ETag(基于 mtime+size),动态内容则需要应用自己算(内容 hash 是稳妥做法)。
# 手动模拟一次协商
curl -sI https://laohand.com/index.html | grep -i etag
curl -sI -H "If-None-Match: <上一步拿到的值>" https://laohand.com/index.html | head -1
# 期望输出: HTTP/1.1 304 Not Modified策略分治:指纹资源与 HTML 各走各路
构建产物带内容 hash(app.a1b2c3.js)→ 内容一变文件名就变,每个 URL 永远只对应一份内容,放心给 `max-age=31536000, immutable`;而 HTML 引用新 hash 的资源,所以 HTML 本身必须 `no-cache`(每次协商),用户才能及时看到新版本。
没指纹的静态资源用短 `max-age`(如 5 分钟)+ 协商兜底;接口响应默认 `private, no-cache`,只有明确可公开缓存的列表类数据才给 `public`。这套分治是「用户秒开」与「发版即生效」兼得的唯一解。
# 典型分治
/app/assets/* → public, max-age=31536000, immutable
/index.html → no-cache
/api/* → private, no-cacheNginx 落地配置
按 location 分流:指纹化静态目录给一年长缓存;HTML 显式 `no-cache`(注意 `expires -1` 输出的是 no-cache 语义);代理后端的响应头按需透传或覆盖。Nginx 静态文件的 ETag 默认开启,无需额外配置。
CDN 场景给共享缓存加 `s-maxage`,源站同时保留更短的 `max-age` 约束浏览器——两层缓存各自的过期时间互不干扰。
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location = /index.html {
add_header Cache-Control "no-cache";
}
# 静态文件 ETag 默认开启,无需额外配置验证与高频排错
验证链路:`curl -I` 看响应头是否符合预期;DevTools Network 的 Size 列显示 (memory cache)/(disk cache) 即强缓存命中,显示 304 即协商命中。
两个高频坑:① DevTools 打开时 Network 面板默认勾选 Disable cache,测出的结果不代表真实用户;② CDN 层另有一套缓存,改了源站响应头还要刷新 CDN 才生效。「改了代码用户看不到新版」九成是 HTML 被强缓存——把 HTML 调成 no-cache 即可根治。
curl -sI https://example.com/app.a1b2.js | grep -i cache-control
# DevTools: Network → Size 列
# (memory/disk cache) = 强缓存命中
# 304 = 协商命中