静态资源加了版本号还命中旧缓存,缓存头该怎么写
给静态资源加版本号后浏览器仍命中旧缓存,问题通常不在版本号本身,而在缓存响应头与文件名策略没配套。要分开看两件事:新鲜期由 max-age 决定,过期后能不能继续用旧副本由验证要求决定。如果只设了长新鲜期而没有要求回源验证,浏览器在过期后仍可能复用陈旧副本;而文件名带版本、路径却没变时,旧的响应也可能还在缓存里。
静态资源的缓存直接决定页面加载速度,也决定高并发下源站要承接多少重复请求,所以这一层的配置值得按对象分开算,而不是全站套一个时长。
浏览器为什么还在用旧文件
一个容易搞错的点:max-age 指的是响应在源站生成后多久内保持新鲜,起算点是生成时刻,不是收到的时刻。经过多层缓存或长时间未访问的资源,实际可用时间比表面看起来短。等它过期,接下来的行为取决于有没有验证要求。
版本号解决的是「同一个 URL 对应了不同内容」这件事。文件内容变了,地址也变了,缓存里那份旧地址的副本自然不再被引用;反之如果版本号只加在查询参数上而路径复用,仍可能撞上旧响应。
新鲜期与验证要求分别由哪个字段管
max-age设新鲜期,期内可直接复用;must-revalidate要求过期后必须回源验证,验证不了就返回 504,而不是把陈旧副本继续送出去;no-store是完全不允许存储,和「存但要求验证」是两回事;immutable表示新鲜期内该响应不会被更新,从而免掉多余的条件请求,它适合配合版本号使用——文件名已经确定了对应的内容版本。
这里有个常见误配:给带版本号的资源同时设 no-store,等于把版本化的收益抵消掉,每次都回源。
共享缓存和私有缓存为什么要分开写
s-maxage 只对共享缓存(CDN、反向代理)生效,并且会覆盖 max-age 与 Expires;私有缓存忽略它。所以当站点在前面挂了反向代理或 CDN 时,只写 max-age 会让共享缓存跟着同一个时长走,改一次样式要等全网刷新的时间会拉长。分开写的目的,是让代理层有更长新鲜期、浏览器有更新更短的新鲜期。
版本号加在什么位置才有效
版本号加在文件名或路径上,比只加查询参数更可靠:部分缓存与加速服务对带参数的地址处理方式不一致。同时要确认发布流程真的产生了新文件,而不是把旧文件重新推一遍。
这与站点的部署方式有关。用 Docker 镜像部署的,静态文件通常在挂载卷里,发布要确认卷内容随版本更新;用宝塔面板或 aaPanel 部署的,改版后要顺带确认反向代理缓存有没有连带刷新。页面 URL 依赖伪静态规则的,文件名策略改动别影响到已有地址;涉及域名或目录迁移时,用 301 重定向把旧地址指向新地址,让缓存与搜索爬虫都能跟过去。
页面本体与静态资源分开配
| 对象 | 新鲜期取向 | 配套字段 | 理由 |
|---|---|---|---|
| 带版本号的静态资源 | 长 | immutable,配合版本化文件名 |
地址已确定内容,无需频繁验证 |
| 无版本号的静态资源 | 短 | 保留验证要求 | 改内容时地址不变,长缓存会拖住更新 |
| 页面 HTML 本体 | 很短或不缓存 | 按需要求验证 | 内容随时可能更新,缓存过长读者看不到新文章 |
| 接口类响应 | 视数据而定 | 谨慎使用共享缓存 | 带用户身份的响应不适合被共享缓存复用 |
页面本体的缓存策略影响最直观:列表页与文章页若被长缓存,读者发布新内容后自己先看不到。反代或 CDN 层还要补的是刷新粒度——能按路径刷,就别用整站清空。
常见问题
问:加了版本号还需要 must-revalidate 吗? 答:可以不强求,但别写成放任陈旧复用。版本化资源更适合长新鲜期加 immutable,页面本体仍要保留验证要求。
问:怎么确认浏览器用的是哪一份缓存? 答:看响应的实际来源与头部,配合无痕窗口对比,比只看本地文件时间可靠。
问:改一次样式就要换一次版本号吗? 答:只要发布流程能自动产生新文件名,就不必手工记,重点是内容与地址要保持一致地变。