Service Worker 更新后如何避免旧缓存继续返回
Service Worker 更新后仍返回旧页面,通常不是“浏览器没有更新”,而是旧 worker、旧 Cache API 条目和仍被旧 controller 控制的页面叠在了一起。稳定做法是让缓存名跟随版本变化,在 activate 中删除旧名,再决定页面何时接管新 worker。
- 用版本化缓存名区分资源集合,不要在新旧版本间复用含义不清的固定名称。
- 清理旧缓存放在
activate,并通过event.waitUntil()等待清理完成。 skipWaiting()解决等待激活,clients.claim()解决已打开页面的控制权;两者不是同一个动作。
先把旧缓存问题拆成三个状态
一次更新至少涉及三个对象:浏览器正在安装的新 worker、仍负责当前页面请求的旧 worker,以及 Cache API 中按名称保存的资源。新 worker 安装期间旧 worker 继续工作,因此不能在 install 阶段删除旧缓存,否则旧页面可能突然拿不到自己的资源。
另一个容易忽略的边界是页面控制权。worker 激活后,已经打开的文档未必立刻换 controller;如果页面没有重新加载,看到旧内容并不能直接证明缓存清理失败。

用版本化缓存名让 activate 负责清理
下面的最小实现把预缓存和清理分开。install 只创建新集合;activate 列出所有缓存名,删除不在保留名单中的名称。把每个异步动作交给 waitUntil,浏览器才会把安装或激活视为仍在进行。
const CACHE_NAME = "app-static-v3";
const ASSETS = ["/", "/app.js", "/styles.css"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
// 新版本只写入自己的缓存,避免干扰仍在服务旧页面的 worker。
return cache.addAll(ASSETS);
}),
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys
.filter((key) => key.startsWith("app-static-") && key !== CACHE_NAME)
.map((key) => {
// 删除只属于本应用且不是当前版本的缓存,不碰其他用途的名称。
return caches.delete(key);
}),
),
),
);
});
这里的判断故意保留前缀范围。直接删除所有非当前缓存,可能误删同一 origin 下由其他功能维护的 CacheStorage 名称。若应用把图片、接口响应和静态文件分成多类缓存,应把各类当前名称都放进保留集合,而不是只留下一个。
skipWaiting 和 clients.claim 要不要一起用
self.skipWaiting() 让等待中的新 worker 尽快进入 active,clients.claim() 则让激活后的 worker 尝试接管作用域内的现有页面。前者改变 worker 生命周期,后者改变页面 controller。两者都打开时,旧页面可能在一次会话中切换到新代码;如果页面状态不能跨版本兼容,更稳妥的做法是只提示用户刷新,或在用户完成编辑后再发送消息让新 worker 执行。
self.addEventListener("install", (event) => {
event.waitUntil(
// 只有确认新旧脚本可以共同处理当前页面时,才立即跳过等待。
self.skipWaiting(),
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
(async () => {
// 清理完成后再接管页面,减少新代码读到未准备好资源的窗口。
await removeOldCaches();
await clients.claim();
})(),
);
});
如果选择“下次导航生效”,可以不调用这两个方法,让浏览器按默认生命周期完成切换;如果选择“立即生效”,应同时考虑未保存表单、内存状态和 API 响应格式是否兼容。更新策略是产品行为,不是越快接管越好。

仍然返回旧内容时按三层证据排查
| 看到的现象 | 优先检查 | 对应判断 |
|---|---|---|
| 缓存列表里有 v2、v3 | activate 是否执行,waitUntil 是否等待删除 | 清理逻辑未完成或未被当前 worker 接管 |
| 缓存只有 v3,页面仍旧 | navigator.serviceWorker.controller 与页面是否重载 | 页面控制权或内存中的旧文档未切换 |
| controller 已更新但接口旧 | fetch 事件是否 cache-first、HTTP Cache-Control 是否过期 | 返回来源可能是 fetch 策略或 HTTP 缓存,不是旧 Cache API 名称 |
对 HTML 入口和带版本指纹的 JS/CSS,常见组合是“网络优先的导航请求 + 版本化静态缓存”。对于用户数据接口,不要照搬静态资源的 cache-first;更新时先确认响应状态和内容类型,再决定是否写入缓存。这样即使某一层仍有旧数据,也能快速定位到底是 worker 生命周期、页面 controller,还是请求策略造成的。
常见问题
删除旧缓存后为什么当前页面没有马上变新?
缓存清理只处理 Cache API 条目,不会自动重建已经打开的文档。检查 controller 是否变化,并在合适时机重新加载页面。
缓存名每次发布都加版本就一定安全吗?
不一定。还要在 activate 删除旧名、控制缓存数量,并确认 install 的资源清单完整,否则可能只是把旧问题换了一个名称。
skipWaiting 是否应该默认开启?
只有新旧代码能兼容当前页面状态时才适合立即接管。编辑器、支付流程或长表单更适合提示刷新并等待用户确认。
Go TLS 客户端只在生产环境报证书错误怎么办
- 上一篇
- Go TLS 客户端只在生产环境报证书错误怎么办
- 下一篇
- LiblibAI下载LoRA后怎么在本地WebUI加载?从底模匹配到权重测试
-
- 文章 · 前端 | 2小时前 | Fetch API · ReadableStream · 流式渲染 · Fetch ReadableStream TextDecoder 前端流式渲染
- ReadableStream 如何把 Fetch 响应分块显示到页面
- 304浏览 收藏
-
- 文章 · 前端 | 3小时前 | 前端 · View Transition API · 浏览器 API · 前端动画 View Transition API view-transition-name
- 浏览器 View Transition 如何只动画列表中的一个元素
- 483浏览 收藏
-
- 文章 · 前端 | 20小时前 | 前端 · web components · 生命周期 · 自定义元素 · document Web Components Custom Elements adoptedCallback adoptNode importNode
- Web Components adoptedCallback 什么时候触发
- 382浏览 收藏
-
- 文章 · 前端 | 21小时前 |
- Service Worker Cache API 更新资源如何避免旧缓存覆盖
- 215浏览 收藏
-
- 文章 · 前端 | 22小时前 |
- WebSocket close code 1006 为何没有服务端原因
- 154浏览 收藏
-
- 文章 · 前端 | 1天前 | 异步编程 · IndexedDB · 前端存储 · 事务 await IndexedDB TransactionInactiveError
- IndexedDB 事务为何不能跨 await
- 221浏览 收藏
-
- 文章 · 前端 | 1天前 |
- BroadcastChannel 标签页关闭后为何收不到消息
- 199浏览 收藏
-
- 文章 · 前端 | 1天前 | javascript · IntersectionObserver · 无限滚动 · 滚动加载 IntersectionObserver 前端性能
- IntersectionObserver 观察列表底部为何重复触发
- 106浏览 收藏
-
- 文章 · 前端 | 1天前 | 前端 · url · javascript · URLSearchParams append getAll
- URLSearchParams 生成重复参数怎么保留多个值
- 483浏览 收藏
-
- 文章 · 前端 | 1天前 | 前端开发 · 网络请求 · Fetch API · 异步取消 · ReadableStream · Fetch AbortController ReadableStream Response.Body AbortError
- AbortController 取消 fetch 后如何清理未消费的 response body
- 492浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 99次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 28次使用
-
- AGI-Eval
- AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
- 6次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 253次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 180次使用
-
- 前端发布后白屏复盘:Service Worker 缓存旧入口导致 JS 资源 404
- 2026-06-30 469浏览
-
- 前端 Cache API 如何按版本清理离线资源:请求匹配、缓存更新与失效回滚
- 2026-08-28 418浏览
-
- Service Worker 更新后旧缓存为什么还在使用
- 2026-09-07 251浏览
-
- 前端 Service Worker 的 skipWaiting 和 clientsClaim 怎么安排版本切换
- 2026-09-08 410浏览
-
- Service Worker 更新后旧缓存不消失怎么设计版本清理
- 2026-09-09 127浏览

