You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Safari中Service Worker自定义离线页功能异常问题求助

Safari下PWA离线页异常问题的解决方案

我之前做PWA离线功能时也踩过Safari这个坑,你的问题核心就是Safari特有的请求缓存行为:当无网时请求page1.html失败,Service Worker返回离线页后,Safari会把这个失败请求对应的离线页响应存在自身的HTTP缓存里。哪怕之后恢复网络,再次请求page1.html时,它会直接调用这个缓存的离线页,完全跳过Service Worker的fetch事件,也不走网络请求。而page2.html能正常加载,是因为它没在无网时被请求过,没有被Safari缓存失败响应。

解决方案

要解决这个问题,我们需要从两个关键方向调整Service Worker逻辑:

  1. 给离线页响应添加缓存控制头,禁止Safari缓存失败场景下的离线页;
  2. 针对页面导航请求做特殊处理,确保恢复网络后优先拉取最新在线内容。

修改后的完整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);
          });
        });
      })
    );
  }
});

关键修改点说明

  1. 区分导航请求:通过event.request.mode === 'navigate'识别页面跳转行为,针对性调整逻辑,不影响静态资源的正常缓存;
  2. 优先走网络:导航请求先尝试网络请求,成功就更新缓存并返回在线内容——确保恢复网络后能直接拉取最新页面;
  3. 禁用离线页缓存:返回离线页时手动添加Cache-Control: no-store等头,彻底阻止Safari缓存这个失败场景下的响应;
  4. 调整缓存逻辑顺序:原逻辑是先查缓存再走网络,导航请求改为先走网络再查缓存,更符合用户恢复网络后需要最新内容的预期。

验证步骤

按照你之前的重现流程测试:

  1. 联网访问网站;
  2. 断开网络,访问page1.html,显示离线页;
  3. 返回首页,恢复网络;
  4. 再次访问page1.html,应该能正常加载在线页面了。

内容的提问来源于stack exchange,提问作者ohladkov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:31:30