一张表看懂 HTTP 缓存:到底谁说了算

做站的人迟早会遇到这个场景:本地改完 CSS,刷新一看没变;或者明明发了新版,别人打开还是老样子,还截图来问你。

十有八九是缓存。而缓存这件事,浏览器其实很听话——你说能存多久,它就存多久。问题通常出在头没写对,或者写了但和文件名对不上。

两种缓存,别混在一起

强缓存:压根不发请求。浏览器看本地还没过期,直接用。

协商缓存:发一个很轻的请求去问服务器“我这份还能用吗”。能用就回 304,不带内容;不能用才回 200 和新文件。

一个省流量,一个省带宽但省不了往返。实际项目里,两者是搭配用的。

强缓存:管“多久不用问”

Cache-Control: max-age=2592000, immutable

意思是 30 天内别来问我,而且我保证这期间内容不会变(immutable)。

老一点的写法是 Expires,给一个绝对时间:

Expires: Wed, 21 Oct 2026 07:28:00 GMT

Cache-Control 优先于 Expires。现在没必要再用 Expires 了,除非你要兼容很老的客户端。

还有两个容易忽略的修饰:

  • s-maxage=600:只对 CDN、反向代理这类共享缓存生效,优先级高于 max-age。
  • public / private:能不能被共享缓存存。带登录信息的响应应该用 private。

协商缓存:管“过期了怎么办”

过期之后不是直接重下,而是先问一句。

服务器第一次返回时给个指纹:

ETag: "a1b2c3"
Last-Modified: Sat, 17 Oct 2026 09:12:00 GMT

浏览器下次带上:

If-None-Match: "a1b2c3"
If-Modified-Since: Sat, 17 Oct 2026 09:12:00 GMT

服务器一比对,没变就回 304 Not Modified,响应没有 body,只有几十字节的头。变了才回 200 加新内容。

ETag 比 Last-Modified 准。原因是时间只精确到秒,而且文件内容没变、只是被重新保存一次,Last-Modified 也会变。所以两个都有时,ETag 优先。

一张表

指令作用典型值
max-age强缓存多久,单位秒31536000(一年)
s-maxage共享缓存多久,覆盖 max-age86400
no-cache可以存,但每次用之前必须问—
no-store完全不许存支付、隐私页
must-revalidate过期后不许拿旧的凑合—
immutable有效期内绝不重新验证带指纹的静态资源
private只有浏览器能存,CDN 不行登录后的页面
public谁都能存公共静态资源

no-cache 这个名字特别容易误解:它不是“不缓存”,而是“存可以,但用之前必须问”。真不想存,用 no-store。

静态资源:一年 + 指纹

CSS、JS、图片这类东西,最省事的组合是:

  1. 文件名里带内容哈希,比如 main.a1b2c3d4.css;
  2. 响应头给一年 + immutable。
Cache-Control: public, max-age=31536000, immutable

内容一变,哈希就变,文件名就变,浏览器自然会当成新文件去取。旧文件没人引用,CDN 到期自己就清了。用哈希换缓存,是这里最关键的一步——不带指纹的文件名不要配长缓存,否则你改完用户一年都看不到。

HTML:永远别长缓存

HTML 是入口,它决定引用哪个版本的资源,所以它必须能被及时更新。

Cache-Control: no-cache

配合 ETag,正常情况就是一个 304,成本很低。

常见的坑是:HTML 长缓存了,里面还引用着旧哈希的 CSS——用户清了缓存也没用,因为拿到的新 HTML 又指回旧文件。长缓存只能给带指纹的资源,不能给入口页。

几个容易踩的点

  • 刷新行为不一样:普通刷新(F5)通常会跳过强缓存去验证,强制刷新(Ctrl+Shift+R)会连缓存一起清。所以“我这边刷新是好的”不能证明用户那边没问题。
  • Vary 没写:同一 URL 返回不同内容(比如按 Accept-Encoding 或语言),共享缓存可能把 A 的响应给 B。至少写 Vary: Accept-Encoding。
  • CDN 有自己的一层:源站的 Cache-Control 不一定被完全尊重,有的 CDN 会按自己的规则覆盖。排查时先看响应头里到底是哪一层加的。
  • 304 也会更新缓存时间:拿到 304 后,浏览器会把这份缓存的有效期重新算一遍。

一句话总结:给带指纹的资源长缓存,给 HTML 短缓存,剩下的事情交给 ETag。 三句话能解决九成的“改了看不到”。