本文要点
要理解 immutable 解决的是什么问题,先看普通重新加载时浏览器发了什么
immutable 只在“这个 URL 的内容确实不会变”这个前提下才成立
immutable 的适用对象,是那些 URL 里已经带了版本标识、内容按设计不会变的静态子资源

本文目录
immutable 声明的是“这个 URL 的内容永不改变”
先给有条件的答案:immutable 是 Cache-Control 的一个指令,用来明确告诉缓存,不需要对这个响应做重新验证,因为内容永远不会改变。
按 MDN 的 HTTP 缓存说明,永远不会改变的内容应该被赋予一个较长的 max-age,方法是使用缓存破坏,也就是在请求 URL 中包含版本号、散列值等。但是,当用户重新加载时,即使服务器知道内容是不可变的,也会发送重新验证请求。为了防止这种情况,immutable 指令可用于明确指示不需要重新验证,因为内容永远不会改变。
这里有两个词要分清。max-age 管的是“这份响应在多久之内算新鲜、可以直接复用”。immutable 管的是另一件事:当用户主动重新加载时,浏览器要不要照常发一次验证请求。两者回答的不是同一个问题,所以它们经常一起出现,而不是互相替代。
一个前提:immutable 说的是“这个 URL 的内容不会变”,不是“这个文件永远不会被更新”。更新内容时,做法是换一个新的 URL,而不是改动同一个 URL 下的内容。本文只讲这一条指令的含义和它配合缓存破坏的场景,不涉及 no-store,也不涉及 CDN 配置。
为什么内容不变还要专门声明:重新加载会触发验证
要理解 immutable 解决的是什么问题,先看普通重新加载时浏览器发了什么。
按 MDN 缓存文档给出的简化视图,浏览器重新加载期间发送的请求大致是:
GET / HTTP/1.1
Host: example.com
Cache-Control: max-age=0
If-None-Match: "deadbeef"
If-Modified-Since: Tue, 22 Feb 2022 20:20:20 GMT请求里的 max-age=0 表示“只重用存活时间为 0 或更少的响应”,所以中间存储的响应不会被直接复用。请求再通过 If-None-Match 和 If-Modified-Since 做验证:如果内容没变,服务器返回 304 Not Modified;如果变了,返回 200 OK 和新内容。
问题就出在这里:即使这个资源的内容按设计永远不会变,重新加载时浏览器仍然会走一遍验证。服务器要处理这次请求,客户端要等一个往返。immutable 的作用就是把这个“明知不会变却还要问一次”的动作省掉。
一个自建情境(示例,不是对某个真实网站的记录):你在练习目录里维护一个自建页面,页面引用一个带版本号的自建脚本文件。第一次打开后,你按普通重新加载,在开发者工具的 Network 面板里观察这个脚本请求有没有发出、返回的是 304 还是直接复用。下面所有文件名和内容都是练习用的自建数据。
immutable 要和缓存破坏一起用,单用没有意义
immutable 只在“这个 URL 的内容确实不会变”这个前提下才成立。要让这个前提成立,靠的是缓存破坏。
按 MDN 的说明,缓存破坏是一种通过在内容更改时更改 URL 来使响应在很长一段时间内可缓存的技术,可以应用于所有子资源,例如图像。文档给出的几种写法是:文件名中的版本号、查询中的版本号、文件名散列、查询散列。
把这两件事连起来看,逻辑是:
1. 内容变了,就换一个新 URL。
2. 因为 URL 变了,缓存会把它当成一个新资源,不会复用旧响应。
3. 所以同一个 URL 下的内容可以放心地声明为永不改变。
4. 既然永不改变,重新加载时就没有必要再验证,于是加上 immutable。
如果只加 immutable、不做缓存破坏,那么内容一旦更新,同一个 URL 下的旧响应可能长时间留在缓存里,而浏览器又因为 immutable 不去验证,用户就看不到新内容。所以这两个做法是一套,不是两个可选项。
MDN 给出的组合示例是:
# bundle.v123.js 文件响应
Cache-Control: public, max-age=31536000, immutable
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: "YsAIAAAA-QG4G6kCMAMBAAAAAAAoK"这个例子里,max-age 给得很长,immutable 负责在重新加载时跳过验证,Last-Modified 和 ETag 仍然保留,用于其他需要验证的场景。
哪些资源适合加 immutable:带版本号的静态子资源
immutable 的适用对象,是那些 URL 里已经带了版本标识、内容按设计不会变的静态子资源。
按 MDN 的说明,最适合缓存的资源是静态不可变文件,其内容永远不会改变;而对于会变化的资源,通常的最佳实践是每次内容变化时都改变 URL,这样 URL 单元可以被缓存更长的时间。文档举的子资源例子是 JavaScript 和 CSS,并说明这种技术可以应用于所有子资源,例如图像。
可以按下面几条判断(以下为编辑整理的判断方向,不是官方判定口径):
1. 这个资源的 URL 里有没有版本号或散列值。有,才具备“内容不变”的前提。
2. 它是不是被页面引用的子资源,而不是用户直接打开的主文档。
3. 它的内容更新时,你能不能换一个新 URL,而不是原地覆盖。
三条都满足,才适合考虑加 immutable。任何一条不满足,都不应该把它当成“永不改变”的资源来处理。
一个自建示例(练习数据,不是真实项目):在练习目录里放两个自建文件,一个叫 bundle.v123.js,一个叫 build.v123.css,页面里按带版本号的名字引用它们。这样做的目的是让 URL 本身携带版本信息,而不是靠修改同一个文件名来更新内容。
主文档为什么不适合:它的 URL 没法加版本号
immutable 不适合用在 HTML 主文档上,原因和缓存破坏的适用范围有关。
按 MDN 的说明,与子资源不同,主资源不能使用缓存破坏,因为它们的 URL 不能像子资源 URL 一样被修饰。如果存储了 HTML 本身,即使在服务器端更新内容,也无法显示最新版本。
这句话给出的是一个结构性限制:子资源的 URL 写在 HTML 里,你可以随时把它改成带新版本号的名字;而用户直接访问的那个 HTML 地址,你不能要求每个用户都换一个新地址去打开。所以主文档没有“换 URL 就等于换内容”这条退路。
对这种情况,MDN 给出的方向是使用 no-cache,而不是 no-store,因为目标不是不存储,而是希望它始终是最新的;同时加上 Last-Modified 和 ETag,让客户端可以发条件请求,内容没更新时返回 304 Not Modified。
所以判断顺序可以这样定:先看这个资源能不能通过换 URL 来更新。能,才谈得上 immutable;不能,就走验证那条路。本文不展开 no-cache 和 no-store 的对比,只说明主文档为什么不适用 immutable。
检查与排查:怎么确认 immutable 有没有按预期起作用
核对 immutable 的效果,可以在浏览器开发者工具里做,不需要改任何系统设置。
一个自建情境(示例,不是对某个真实网站的记录):你在练习目录里维护一个自建页面,引用一个带版本号的自建脚本,服务器返回的响应头里写了 Cache-Control: max-age=31536000, immutable。下面所有观察都围绕这个自建页面展开。
可以按这几步核对:
1. 打开 Network 面板,刷新页面,找到那个带版本号的脚本请求,点开响应头,确认 Cache-Control 这一行里确实有 immutable。
2. 做一次普通重新加载,观察这个脚本请求有没有再次发出验证请求。
3. 如果仍然看到验证请求,先检查这个资源是不是主文档,或者 URL 里有没有版本标识。
4. 如果内容更新后用户仍看到旧版本,先检查更新时有没有换 URL。
需要说明的是,MDN 文档里有一条实现差异的备注:Chrome 已更改其实现方式,不再在重新加载子资源时执行重新验证,而非直接实现该指令。也就是说,不同浏览器对“重新加载时要不要验证子资源”的处理并不完全一致,你在 Network 面板里看到的请求数量可能因浏览器而异。本文不给出各浏览器的具体行为对照表,因为资料没有提供这部分内容。
排查时还要注意一点:immutable 只影响重新加载时的验证行为,它不改变 max-age 的计算,也不负责删除已经存储的响应。
使用限制:immutable 不能解决“删掉旧缓存”的问题
immutable 有一个容易被忽略的边界:它让缓存更放心地长期保存响应,但同时也让服务器更难主动收回这份响应。
按 MDN 的说明,基本上没有办法删除用很长的 max-age 存储的响应。一旦响应被存储,服务器就无法执行任何操作,因为由于缓存,不再有请求到达服务器。文档提到的方法之一是使用不安全的方法(例如 POST)发送对同一 URL 的请求,但对于许多客户端而言这很难实现;Clear-Site-Data: cache 标头及其指令值可用于清除浏览器缓存,但对中间缓存无效。响应将一直保留在浏览器缓存中,直到 max-age 过期为止,除非用户手动执行刷新、强制刷新或清除历史记录操作。
这段说明和 immutable 的关系是:immutable 是在“内容永不改变”这个前提下才成立的。如果这个前提不成立,那么加 immutable 只会让旧内容留得更久、更难被替换。
所以使用限制可以归纳成三条:
1. 只对带版本号或散列值、内容按设计不变的静态子资源使用。
2. 更新内容时换 URL,不要原地覆盖。
3. 不要把它用在需要频繁更新、或者需要服务器随时收回的响应上。
本文只讲 immutable 的含义、它和缓存破坏的配合关系,以及上面这些限制。不涉及 no-store 的用法,也不涉及 CDN 或托管缓存的具体配置。
观看知识要点视频 18 秒
18秒无旁白要点导览,文字来自已审核正文;完整步骤、条件及参考资料见下方。
参考资料
以下原始资料提供本文依据;请结合日期、适用条件和产品说明查阅。
请结合原始资料的适用条件阅读。
返回浏览器工具 →