pwa后台静默更新关键在于service worker的install→wait→activate流程:新sw下载后进入waiting状态,待用户关闭再打开页面时自动activate并接管,配合index.html禁用http强缓存、静态资源带内容哈希、sw脚本避免cdn缓存,实现无感升级。

PWA 实现后台静默更新资源、并在下次启动时生效,关键不在于“立刻刷新”,而在于让新版本资源提前缓存就绪,等用户关闭再打开时,Service Worker 自动接管并使用新缓存。整个过程对用户无感知,也不打断当前会话。
核心机制:Service Worker 的 install → wait → activate 流程
浏览器在页面加载时会比对已注册的 Service Worker 脚本(如 sw.js)与服务器上最新版本的哈希值。一旦发现不同,就会下载并安装新 SW,但它不会立即取代旧 SW,而是进入 waiting 状态。此时新资源已缓存,但旧 SW 仍在控制页面。只有当所有旧页面关闭、新页面打开时,新 SW 才会 activate 并生效。
- install 阶段:打开新缓存(如 my-app-v2.1.0),把新版 HTML、JS、CSS、WASM 等资源 addAll 进去
- activate 阶段:调用 caches.delete() 清理旧缓存(如 my-app-v1.9.5),再执行 clients.claim() 接管新打开的页面
- fetch 阶段:始终优先返回当前激活缓存中的资源,确保离线可用、启动秒开
确保静默更新真正生效的三个前提
很多项目卡在“看似更新了,实则没换”,问题常出在这三处:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- index.html 必须禁用 HTTP 强缓存:它不能被浏览器本地长期缓存(如 Cache-Control: max-age=31536000)。应设为 no-cache 或 max-age=0, must-revalidate,否则用户打开的还是旧 HTML,里面引用的仍是旧 JS 文件名
- 静态资源需带内容哈希:JS/CSS/WASM 文件名应包含 hash(如 app.a1b2c3.js),这样即使 index.html 缓存失效,新 HTML 也会请求新文件,触发 SW 缓存新版本
- SW 脚本本身要避免 CDN 缓存:注册时用 updateViaCache: 'none'(Next.js)、或服务端配置 Cache-Control: no-cache 响应头,确保每次都能比对真实更新
无需用户操作,下次启动即生效的落地方式
不需要弹窗、不强制 reload,只需保证用户关闭标签页(或浏览器)后重新访问,就能自然切换到新版:
- 在 sw.js 的 activate 事件中,调用 self.clients.matchAll() 检查是否有活跃客户端;若有,可跳过 claim,留待下次——这是默认行为,无需额外代码
- 确保 manifest.json 中 "display": "standalone",这样从桌面图标启动时,会以全新上下文加载,自动触发新 SW 激活
- 如果使用构建工具(如 Vite + vite-plugin-pwa),启用 registerType: 'autoUpdate',插件会在页面空闲时主动调用 registration.update() 检查更新,保持 SW 后台同步
验证是否真正在后台更新了
打开 Chrome DevTools → Application → Service Workers,观察:
- 右侧显示 “Waiting” 表示新 SW 已下载缓存就绪,只等下一次页面加载
- 点击 “Skip Waiting” 可手动触发激活(仅开发调试用)
- 刷新页面后,若顶部显示 “Deactivated” 旧 SW,且新 SW 状态变为 “Active”,说明切换成功
- Application → Cache Storage 中,应能看到两个缓存名(如 my-app-v1.9.5 和 my-app-v2.1.0),后者包含全部新版资源










