跳到主要内容

Cache-Control 缓存控制头实战:max-age、immutable 与 CDN 配置怎么写

讲清楚 Cache-Control 头里 max-age、no-cache、no-store、public/private、immutable 各干什么,静态资源长缓存和动态内容怎么分开调,配合 CDN 提速又不卡住更新。

发布于 作者 李雷
#HTTP #缓存 #性能优化 #CDN #Web 开发

Cache-Control 缓存控制头实战:从 max-age 到 immutable

缓存配错,代价分两头。配太松,用户每次访问都重新下载几百 KB 的 JS,首屏慢、带宽白烧;配太狠,改了价格、改了文案,用户却被钉在一周前的旧副本上,客服电话打爆。Cache-Control 这个响应头就是夹在这两头中间的开关,可它的指令组合多到容易写错。这篇把每个指令讲透,顺带给几套能直接抄的典型配置。

max-age 永远是秒,别当成分或时

最常踩的坑是单位。max-age 是一个不带后缀的整数,单位永远是秒。一小时写 max-age=3600,一天 max-age=86400,一年 max-age=31536000。我自己早年踩过一次:把 max-age=3600 当成一小时没错,但顺手把另一个值 max-age=60 当成一分钟去理解隔壁的 3600,结果心算全乱,缓存时长差了 60 倍。秒数确实难读,所以填的时候用「天 / 时 / 分 / 秒」换算,比硬背 31536000 这串数字靠谱。

静态资源长缓存:max-age=31536000 配 immutable

带内容哈希的静态资源,是缓存收益最大的地方。比如打包器产出 app.4f3a1c.js,文件名里嵌了哈希,新构建必然换 URL。这种文件可以放心缓存一年:

Cache-Control: public, max-age=31536000, immutable

immutable 是关键。它告诉浏览器:在 max-age 的新鲜期内,这个正文永远不变,所以哪怕用户手动刷新页面,浏览器连那次条件校验请求(If-None-Match)都跳过,直接用本地副本。少一次往返,弱网下能省下肉眼可见的几百毫秒。

但有一条铁律:只有文件名带哈希才能这么干。如果你的资源 URL 是固定的 /static/main.js,一年的 immutable 缓存会把用户死死钉在旧代码上,发新版本他们也收不到。要么给文件名加哈希,要么把 max-age 缩短到几分钟。另外 immutable 必须配一个长 max-age,没有 max-age 它就没有可生效的窗口,等于白写。

动态内容:no-cache 和 no-store 的区别

很多人把这两个搞混,其实差别很大。

no-cache 不是「不缓存」。浏览器照样把响应存下来,只是每次复用前必须回源核对(靠 ETag 或 Last-Modified),内容没变就命中 304 Not Modified,省下整个正文的传输。适合一周改几次、绝不能显示过期价格的营销落地页。

no-store 才是真的「一个字节都不留」,禁止写进任何缓存或磁盘。银行余额、一次性令牌、签名下载链接这类敏感内容才用它。

要新鲜又想省校验成本,选 no-cache;真正敏感的,才上 no-store。顺带一个常见错误:no-store 配 max-age 是矛盾的,no-store 既然禁止存储,max-age 就没有东西可以保鲜,会被悄悄忽略掉。

public、private 和 CDN 边缘缓存

public 和 private 决定谁能存这份响应。private 表示只有用户自己的浏览器能缓存,CDN、共享代理一律不许碰,适合带个人数据的页面。public 则允许共享缓存一起存。

真正的提速空间在 CDN。你可以让浏览器和 CDN 边缘走两套时长:

Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=600

max-age=60 管浏览器,s-maxage=3600 专门给共享缓存(CDN)。这样浏览器端只留一分钟保证用户看到的相对新,CDN 边缘却留一小时,扛住流量高峰、少回源。stale-while-revalidate=600 再加一层:max-age 过期后的 600 秒内,缓存可以先把略旧的副本秒发给当前访客,同时在后台悄悄抓新副本,下一位访客就拿到刷新后的。一点点陈旧,换来零阻塞校验,API 响应特别吃这一套。

几套能直接抄的典型配置

  • 指纹化静态资源(JS / CSS / 图片):public, max-age=31536000, immutable
  • HTML / 每次需校验的页面:no-cache
  • 短缓存 API + 后台刷新:public, max-age=60, s-maxage=3600, stale-while-revalidate=600
  • 敏感接口 / 账户页:no-store

写完别忘了验证。把站点跑起来后,用 HTTP 响应头提取工具 抓一下线上真实返回的头,核对 Cache-Control 到底有没有按预期生效,有没有被中间层覆盖。这一步比盯着配置文件猜要稳得多。

如果你嫌手写逗号、空格、指令顺序烦,也容易把 no-store 和 max-age 这种矛盾组合写进去,可以用 Cache-Control 构建器 勾指令、填时长,它会按惯用顺序拼好整条头,并在你勾出矛盾组合的瞬间提示,还能一并复制出 Nginx 的 add_header 和 Apache 的 Header set 写法。

缓存这东西,理解了 max-age 是秒、immutable 只配哈希文件、no-cache 与 no-store 各管一摊,再分清浏览器和 CDN 两层时长,基本就不会出大错了。剩下的就是针对每类资源选对那几个指令,然后上线后实测一遍。


Made by Toolora · Updated 2026-06-13