Service Worker 高级缓存策略
很多团队上 Service Worker,只做了一件事:把静态资源缓存住。结果离线确实能打开页面,但上线后很快遇到新问题:用户一直拿旧版本、接口缓存错乱、修了 bug 却半天生效不了。
这说明缓存不是“存起来就好”,而是一个关于一致性、速度和可恢复性的系统设计。
1. 不同资源类型,必须用不同缓存策略
一套缓存规则打全站,几乎一定会出事。
更稳的分法是:
| 资源类型 | 推荐策略 | 原因 |
|---|---|---|
| 构建产物 JS/CSS | Cache First | 文件名带 hash,可长期复用 |
| 页面 HTML | Network First | 保证版本更新及时生效 |
| API 数据 | Stale While Revalidate 或 Network First | 兼顾实时性与可用性 |
| 图片字体 | Cache First + 体积限制 | 命中率高,节省流量 |
| 离线页 | Precache | 网络断开时兜底 |
Service Worker 的关键不是背策略名,而是明白不同资源的更新风险不同。
2. 预缓存负责“底线可用”,运行时缓存负责“体验变快”
预缓存更适合:
- app shell
- 关键字体
- 离线页
- 站点 logo 和基础图标
运行时缓存更适合:
- 用户最近浏览过的内容
- 图片列表
- 非关键接口结果
如果你把所有内容都塞进 precache,首装体积会很重;如果完全不做 precache,断网时又没有基本兜底。
3. 一个可维护的实现,不要直接把业务逻辑堆在 fetch 事件里
可以先把策略抽象成函数:
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
if (url.pathname.startsWith('/api/')) {
event.respondWith(networkFirst(event.request, 'api-cache'))
return
}
if (event.request.destination === 'image') {
event.respondWith(cacheFirst(event.request, 'image-cache'))
return
}
})
这样后面做版本升级和问题排查时,不会变成一团不可维护的条件分支。
4. 一个常见失败案例:新版本上线了,老用户却一直拿旧页面
这类事故非常常见:
- 首页 HTML 也被 Cache First 了。
- 用户第二次访问时直接命中旧缓存。
- 新构建已部署,但页面入口没更新。
- 用户看到的是“旧 HTML + 新静态资源”混合状态,开始出现奇怪 bug。
根因是把 HTML 当成静态资源长期缓存。
修复方式通常是:
- 页面文档改成 Network First。
- 使用版本号隔离缓存命名。
- 在新 SW 激活时清理旧 cache。
5. 更新策略不能只靠自动替换,要考虑用户正在操作的状态
如果你的产品是内容编辑器、表单、购物车或长表单流程,Service Worker 发现新版本后不能直接粗暴刷新。
更稳的做法是:
- 新版本准备好时先提示“有更新可用”。
- 等用户完成当前操作,再触发刷新。
- 对关键数据先落本地或服务端再切版本。
6. 离线体验不等于“所有功能都能离线”
离线策略的目标应该是:
- 让用户知道当前网络状态。
- 保证基础访问和最近内容可读。
- 告诉用户哪些操作需要恢复联网后继续。
不要为了“支持离线”把所有交互都伪装成成功,这会制造更大的数据一致性问题。
7. 上线前 Checklist
- 已按 HTML、静态资源、API、图片分开策略。
- HTML 没有被长期 Cache First 锁死。
- cache 命名带版本,激活时会清理旧版本。
- 离线页已预缓存,断网时有明确反馈。
- 关键写操作不会在断网时伪装成功。
- 新版本发布时有更新提示与切换机制。
- 已验证首装体积没有因为 precache 过度膨胀。
- 已在真实弱网与断网环境做过验证。
FAQ
Service Worker 一定能提升性能吗?
不一定。策略设计错了,反而会让用户长期拿旧资源,或者首装体积更大。
API 数据适合 Cache First 吗?
多数场景不适合。除非数据极稳定,否则更推荐 Network First 或 Stale While Revalidate。
每次发布都要清空全部缓存吗?
不建议。更好的做法是按版本命名并精确清理旧 cache。


