作为前端开发我每天到工位的第一件事就是看一看日志。最近我在花猫导航huamaodh.com的异常日志面板里发现我们移动端 H5 的 PWA 离线版本每天都有几十条“更新失败”的报错。追查下来根源在于用户离线时 Service Worker 仍在按部就班地弹更新提示点击后拉不到资源页面直接白屏。为此我们折腾了一套“离线暂停更新”策略上线后离线场景的更新异常归零。下面把落地经验分享出来。背景Service Worker 的“耿直”更新机制先简单回顾一下 Service Worker简称 SW的更新流程浏览器检测到 SW 文件字节级变化触发 install 事件新版本进入 waiting 状态。当所有使用旧 SW 的页面关闭新 SW 激活或者我们手动调用 skipWaiting。通常业务会在检测到 waiting 时提示用户“发现新版本点击刷新”用户确认后执行 skipWaiting reload。问题在于这个流程没有感知网络状态。如果用户在离线或弱网环境下收到更新提示点击刷新后浏览器请求新的 HTML 或路由懒加载 chunk 会直接挂掉页面变成离线恐龙体验很差。离线暂停更新的核心思路在 SW 注册和更新检测环节注入网络状态判断当 navigator.onLine false 时不弹出任何更新提示让新 SW 安静等待。监听 online 事件在网络恢复后再按需提示用户更新。同时给 SW 的 fetch 事件打补丁确保离线时即使跳过等待也不会因请求新资源而崩溃兜底走缓存。落地实现以 Workbox 为例我们项目基于 Workbox代码做了一层薄封装核心逻辑如下1. 注册阶段监听网络状态暂存更新回调javascript// sw-register.js let pendingUpdate false; // 是否有等待中的更新 let updateCallback null; // 提示用户的回调 if (serviceWorker in navigator) { navigator.serviceWorker.register(/sw.js).then(registration { // 当新 SW 进入 waiting 时触发 registration.addEventListener(updatefound, () { const newWorker registration.installing; newWorker.addEventListener(statechange, () { if (newWorker.state installed navigator.serviceWorker.controller) { // 有内容更新且当前已有激活的 SW非首次安装 if (navigator.onLine) { // 在线状态下直接提示 showUpdatePrompt(registration); } else { // 离线时标记有更新暂不提示 pendingUpdate true; updateCallback () showUpdatePrompt(registration); } } }); }); }); } // 监听网络恢复 window.addEventListener(online, () { if (pendingUpdate updateCallback) { updateCallback(); pendingUpdate false; updateCallback null; } }); 2. 提示更新与跳过等待 javascript function showUpdatePrompt(registration) { // 这里可以接入你们团队的 Toast 或对话框组件 const shouldUpdate confirm(发现新版本是否立即刷新); if (shouldUpdate registration.waiting) { registration.waiting.postMessage({ type: SKIP_WAITING }); // skipWaiting 触发后监听 controllerchange 做 reload navigator.serviceWorker.addEventListener(controllerchange, () { window.location.reload(); }); } }在 SW 内部收到 SKIP_WAITING 消息后执行javascript// sw.js self.addEventListener(message, (event) { if (event.data event.data.type SKIP_WAITING) { self.skipWaiting(); } }); 3. SW 侧兜底离线时即使 skip 也走缓存 为防止某些极端场景比如用户在 online 事件触发瞬间点击更新但请求发出时又断网我们在 SW 的 fetch 监听器里加了保底 javascript self.addEventListener(fetch, (event) { event.respondWith( caches.match(event.request).then(cachedResponse { // 有缓存优先返回缓存同时后台尝试网络更新 const fetchPromise fetch(event.request).then(networkResponse { // 更新缓存... return networkResponse; }).catch(() { // 网络失败彻底兜底 return cachedResponse; }); return cachedResponse || fetchPromise; }) ); });这样哪怕更新后的页面主文档或 chunk 无法拉取用户看到的仍然是旧版的缓存内容不会白屏。4. 补一个手动检查入口放在设置页有些用户可能长期离线后恢复网络但浏览器还没主动触发 SW 更新。我们在应用内加了一个手动“检查更新”按钮点击时直接 registration.update()。这段逻辑在检测到离线时也会自动挂起恢复后再执行。实际效果离线场景下“version mismatch”导致的 JS 报错下降 100%。用户主动更新率并没有降低只是更新时机被推迟到网络恢复那几秒内。再多提一嘴我们还在花猫导航的项目面板里接入了离线更新的埋点事件产品经理能随时看到“有多少更新被挂起”、“恢复后更新转化率”迭代方向非常清晰。总结所谓“离线暂停更新”本质上就是把 Service Worker 的更新流程加上网络状态感知把更新提示从一个纯技术事件升级成对用户体验负责的业务决策。如果你的 PWA 或离线 H5 也正被更新白屏困扰不妨试试这套轻量方案。代码改动不大用户体验的提升却是实打实的。