DNS切换后僵尸PWA仍有流量,重新发布如何保障更新可控?
这真是个让人头疼的状况——Service Worker一旦在用户浏览器里扎下根,哪怕你已经把DNS切回旧网站,它还是会按照之前的逻辑接管请求,简直像个甩不掉的“后台幽灵”。先帮你分析下你贴的Workbox代码和问题的关联,再给你梳理重新发布这类PWA时,必须牢牢掌握的更新管控要点:
先看你现有代码的问题
你贴的Workbox路由配置里,第二个路由的正则/[^\.]*/是个大坑:它会匹配几乎所有不带点的URL,包括你的网站根路径、动态页面路径等等。再加上你用了staleWhileRevalidate策略,这个策略的逻辑是“先返回缓存内容,再后台去服务器更新缓存”——也就是说,哪怕你已经切回旧网站,用户访问时Service Worker还是会优先返回之前缓存的PWA版本,后台更新的请求如果打到旧网站,新内容也会被缓存起来,但用户看到的还是旧的缓存内容,强制刷新也没用,因为Service Worker的缓存优先级高于普通浏览器缓存。
另外,这个HTML缓存没有设置任何过期或清理策略,一旦缓存了内容,就会一直留在用户浏览器里,除非手动清理或者Service Worker主动删除。
1. 精准控制路由匹配范围
绝对避免用/[^\.]*/这种过于宽泛的正则,一定要明确指定需要缓存的HTML路径,比如只匹配你的求职平台的动态页面:
// 只匹配/jobs/和/profile/开头的动态页面 workbox.routing.registerRoute( /\/(jobs|profile)\/.*/, workbox.strategies.staleWhileRevalidate({ cacheName: 'html-cache', plugins: [ // 给HTML缓存加过期策略,7天自动失效 new workbox.expiration.Plugin({ maxAgeSeconds: 7 * 24 * 60 * 60, maxEntries: 50, // 最多缓存50个页面 }), ], }) );
这样既不会误缓存旧网站的内容,也能避免缓存无限期留存。
2. 让Service Worker可被更新、可被紧急注销
- 版本化缓存:在Service Worker开头加一个版本常量,所有缓存名都带上版本前缀,比如:
每次发布更新时,只要修改这个版本号,浏览器就会检测到Service Worker文件有变化,自动触发更新流程。const SW_VERSION = 'v20240520'; // 图片缓存名 cacheName: `${SW_VERSION}-images`, // HTML缓存名 cacheName: `${SW_VERSION}-html-cache`, - 主动触发更新:在你的主应用代码里添加检查更新的逻辑,比如页面加载时主动检查:
if ('serviceWorker' in navigator) { window.addEventListener('load', async () => { const registration = await navigator.serviceWorker.register('/sw.js'); // 主动检查更新 registration.update(); // 监听更新完成事件,提示用户刷新 registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { alert('有新的更新可用,请刷新页面!'); } }); }); }); } - 紧急注销机制:如果发布后出问题,要能快速让用户的Service Worker自我销毁。可以部署一个临时的Service Worker,内容如下:
用户下次访问时,这个SW会自动激活,注销自己并清理所有缓存,页面也会强制刷新,恢复到正常状态。self.addEventListener('install', () => { // 跳过等待,立即激活新的Service Worker self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( // 注销当前Service Worker self.registration.unregister() .then(() => { // 清理所有缓存 return caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => caches.delete(cacheName)) ); }); }) .then(() => { // 强制所有打开的页面刷新 return self.clients.matchAll({ type: 'window' }).then(clients => { clients.forEach(client => client.navigate(client.url)); }); }) ); });
3. 选对缓存策略,别乱用staleWhileRevalidate
- HTML文件:如果你的求职平台内容更新频繁(比如新职位、新简历),优先用
networkFirst策略——先请求服务器最新内容,只有网络失败时才用缓存,确保用户看到的是最新内容:
如果内容更新不频繁,再用workbox.strategies.networkFirst({ cacheName: `${SW_VERSION}-html-cache`, plugins: [ new workbox.expiration.Plugin({ maxAgeSeconds: 24 * 60 * 60, // 缓存1天,作为降级方案 }), ], })staleWhileRevalidate,但一定要配合过期策略。 - 静态资源(图片、CSS、JS):用
cacheFirst没问题,但要设置过期和最大条目,避免缓存过多无用资源,你原来的图片缓存配置是合理的,可以保留。
4. 控制Service Worker的作用域
注册Service Worker时,明确指定作用域,只让它接管PWA相关的路径,别影响网站其他部分:
navigator.serviceWorker.register('/sw.js', { scope: '/jobs/' });
这样SW只会处理/jobs/开头的请求,旧网站的其他路径不会被它干扰。如果你的PWA是整个网站,作用域设为/,但一定要确保路由匹配不会覆盖旧内容。
5. 提前测试更新和回滚流程
发布前一定要测试:
- 修改Service Worker版本号,确认浏览器能检测到更新,新SW能正常激活,旧缓存被清理。
- 测试紧急回滚流程:部署注销用的SW,确认能自动清理缓存并注销。
针对你当前的遗留问题
如果还有用户被旧SW困扰,你可以在现在指向旧网站的域名上部署上面提到的紧急注销SW。用户下次访问时,这个SW会自动运行,清理旧缓存、注销自己,然后强制页面刷新,用户就能正常访问旧网站了。
内容的提问来源于stack exchange,提问作者Ken

