Safari中Service Worker自定义离线页功能异常问题求助
Safari下PWA离线页异常问题的解决方案
我之前做PWA离线功能时也踩过Safari这个坑,你的问题核心就是Safari特有的请求缓存行为:当无网时请求page1.html失败,Service Worker返回离线页后,Safari会把这个失败请求对应的离线页响应存在自身的HTTP缓存里。哪怕之后恢复网络,再次请求page1.html时,它会直接调用这个缓存的离线页,完全跳过Service Worker的fetch事件,也不走网络请求。而page2.html能正常加载,是因为它没在无网时被请求过,没有被Safari缓存失败响应。
解决方案
要解决这个问题,我们需要从两个关键方向调整Service Worker逻辑:
- 给离线页响应添加缓存控制头,禁止Safari缓存失败场景下的离线页;
- 针对页面导航请求做特殊处理,确保恢复网络后优先拉取最新在线内容。
修改后的完整Service Worker代码如下:
'use strict'; const CACHE_VERSION = 1; const BASE_URL = location.origin + '/'; const OFFLINE_URL = '/offline.html'; const CURRENT_CACHES = { offline: 'offline-v' + CACHE_VERSION, }; const URLS_TO_CACHE = [ BASE_URL, OFFLINE_URL, ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CURRENT_CACHES.offline) .then(cache => { return cache.addAll(URLS_TO_CACHE); }) .then(() => { return self.skipWaiting() }) ); }); self.addEventListener('activate', event => { let expectedCacheNames = Object.keys(CURRENT_CACHES).map(function (key) { return CURRENT_CACHES[key]; }); event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (expectedCacheNames.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) .then(() => { return self.clients.claim() }) ); }); self.addEventListener('fetch', event => { // 针对页面导航请求(用户点击链接/跳转页面)单独处理 if (event.request.mode === 'navigate') { event.respondWith( caches.open(CURRENT_CACHES.offline).then(cache => { // 导航请求优先尝试网络,成功则更新缓存并返回在线内容 return fetch(event.request) .then(networkResponse => { cache.put(event.request, networkResponse.clone()); return networkResponse; }) .catch(() => { // 网络失败时,先从缓存取目标页面,没有则返回离线页 return cache.match(event.request) .then(cachedResponse => { if (cachedResponse) { return cachedResponse; } // 返回离线页时添加缓存控制头,禁止Safari缓存该响应 return caches.match(OFFLINE_URL).then(offlineResponse => { return new Response(offlineResponse.body, { headers: { ...offlineResponse.headers, 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 'Pragma': 'no-cache', }, status: offlineResponse.status, statusText: offlineResponse.statusText }); }); }); }); }) ); } else { // 非导航请求(如CSS/JS/图片等静态资源)保留原缓存逻辑 event.respondWith( caches.open(CURRENT_CACHES.offline).then(cache => { return cache.match(event.request).then(response => { return response || fetch(event.request).then(networkResponse => { if (networkResponse.status === 200 && networkResponse.type === 'basic') { cache.put(event.request, networkResponse.clone()); } return networkResponse; }).catch(() => { return caches.match(OFFLINE_URL); }); }); }) ); } });
关键修改点说明
- 区分导航请求:通过
event.request.mode === 'navigate'识别页面跳转行为,针对性调整逻辑,不影响静态资源的正常缓存; - 优先走网络:导航请求先尝试网络请求,成功就更新缓存并返回在线内容——确保恢复网络后能直接拉取最新页面;
- 禁用离线页缓存:返回离线页时手动添加
Cache-Control: no-store等头,彻底阻止Safari缓存这个失败场景下的响应; - 调整缓存逻辑顺序:原逻辑是先查缓存再走网络,导航请求改为先走网络再查缓存,更符合用户恢复网络后需要最新内容的预期。
验证步骤
按照你之前的重现流程测试:
- 联网访问网站;
- 断开网络,访问
page1.html,显示离线页; - 返回首页,恢复网络;
- 再次访问
page1.html,应该能正常加载在线页面了。
内容的提问来源于stack exchange,提问作者ohladkov
相关产品推荐
相关产品推荐

