缓存失效与部署策略

通过内容哈希文件名、部署顺序和 Cache-Control 搭配实现"长期缓存 + 即时更新"。

#tech / dev / frontend #type / concept #status / evergreen #platform / browser #tech / dev / deploy

[!info] related notes

缓存失效与部署策略

一句话定义

Cache busting 是在资源内容变化时改变其 URL,使缓存自动失效并获取新版本。核心模式是”长期缓存 + 内容哈希文件名”。

核心矛盾

  • 想要:静态资源长期缓存,减少重复下载
  • 需要:资源更新时用户立即拿到新版本

解决方案:URL 变了 → 缓存自动失效 → 配合长期 max-age 实现最佳性能。

策略一:内容哈希文件名(推荐)

app.3a7f2c1b.js
style.9e8d7c6a.css
logo.a1b2c3d4.png

构建工具配置

Webpack

output: {
  filename: '[name].[contenthash:8].js'
}

Vite:默认行为,无需配置。

工作原理

  1. 文件内容不变 → 哈希不变 → URL 不变 → 命中长期缓存
  2. 文件内容变了 → 哈希变了 → URL 变了 → 浏览器视为新资源
  3. 配合 Cache-Control: public, max-age=31536000, immutable 实现最佳缓存

为什么有效

浏览器缓存键是 URL。URL 变了,即使路径相同,缓存也不会命中旧版本。

策略二:查询字符串(不推荐)

app.js?v=3a7f2c1b

问题

  • 部分 CDN 和代理在缓存时忽略查询字符串
  • app.js?v=1app.js?v=2 可能被某些 CDN 视为同一资源
  • 不如文件名哈希可靠

策略三:版本路径

/v2/app.js
/api/v2/users

适用场景:API 版本控制。静态资源一般不用这种方式。

HTML 问题

index.html 的 URL 由用户访问路径决定,无法加哈希

解决方案

# index.html
Cache-Control: no-cache
  • no-cache = 可以缓存,但每次使用前必须验证
  • 浏览器每次都带 If-None-Match / If-Modified-Since 去问服务器
  • 内容没变 → 304(几乎零开销)
  • 内容变了 → 200 + 新 HTML → 新 HTML 引用新的哈希资源 URL

这就是”长期缓存 + 哈希”模式的完整闭环

index.html(no-cache,每次验证)
  ├── app.3a7f2c1b.js(max-age=1 年,immutable)
  ├── style.9e8d7c6a.css(max-age=1 年,immutable)
  └── logo.a1b2c3d4.png(max-age=1 年,immutable)

发布新版本:
  index.html(内容变了 → 304 变 200)
    ├── app.7f8e9d0c.js(新哈希 → 新 URL → 不命中旧缓存)
    └── style.b2c3d4e5.css(新哈希 → 新 URL)

Cache-Control: immutable

Cache-Control: max-age=31536000, immutable

告诉浏览器:这个资源在整个有效期内绝对不会变。即使用户主动刷新(F5),浏览器也可以跳过重新验证。

浏览器支持

浏览器支持
Firefox支持
Safari支持
Chrome不支持(截至 2026),退化为普通 max-age 行为

Chrome 不支持时,退化为普通长期缓存,用户强刷仍会重新验证。文件名哈希保证了即使验证,拿到的也是正确版本。

部署顺序

正确顺序

  1. 先部署带哈希的资源(JS/CSS/图片)

    • 它们是新 URL,旧缓存不会命中
    • 即使被提前缓存也没问题,因为内容就是新的
  2. 再部署 HTML

    • HTML 引用新的哈希 URL
    • 用户拿到新 HTML 后,会请求新的资源 URL

错误顺序

如果先部署 HTML 再部署资源:

  • 用户拿到新 HTML → 请求新哈希的 JS/CSS
  • 新 JS/CSS 还没部署 → 404

灰度/蓝绿部署

在蓝绿部署中,旧版本和新版本同时存在:

  • 旧 HTML 引用旧哈希的资源(仍在线)
  • 新 HTML 引用新哈希的资源(刚部署)
  • 两套资源可以共存,互不干扰

Service Worker 考虑

如果使用 Service Worker 做预缓存:

  • SW 的 precache manifest 包含资源哈希
  • 新版本 SW 需要与新资源原子更新
  • 旧 SW 可能还在服务旧缓存 → 需要 skipWaiting() + clients.claim() 策略
  • 详见 → Service Worker 与 Cache API

常见误解

  • “查询字符串 ?v=hash 和文件名哈希效果一样” → 不一样,部分 CDN 会忽略查询字符串
  • “设置了 max-age=0 就够了” → 这样每次都要验证,丧失了缓存性能优势
  • “HTML 也要长期缓存” → 不行,HTML 是入口,必须能及时更新
  • “部署新版本后需要让用户清缓存” → 如果正确使用了文件名哈希,完全不需要
创建于 2026/5/23 更新于 2026/5/27