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

Safari中Service Worker无法拦截首次页面加载致重复请求求助

问题描述

我编写了一个简单的Service Worker,用于拦截fetch请求并添加X-Custom-Header请求头,代码如下:

self.addEventListener('install', () => {
    self.skipWaiting();
});

self.addEventListener('activate', event => {
    event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', event => {
    event.respondWith(fetchWithHeader(event.request));
});

function fetchWithHeader(request) {
    const newHeaders = new Headers();
    newHeaders.set('X-Custom-Header', `123`);

    return fetch(request, { headers: newHeaders });
}

我通过以下代码在http://localhost:3000上注册该Service Worker:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', {
    scope: '/',
  });
}

在Chrome和Firefox中,已安装Service Worker后访问http://localhost:3000,后端仅收到一次请求;但在Safari中,后端会收到两次请求。经排查发现,Safari在地址栏输入网址后,会先在Service Worker启动前发送一次请求,待Service Worker启动后再发送一次被其拦截的请求。如何避免该问题?

注:我在Mac和iPhone的不同Safari版本中均测试过,结果一致。

解决方案

1. 提前注册Service Worker,减少启动延迟

将Service Worker的注册代码放在页面<head>中,不等待DOMContentLoaded事件,让浏览器尽早加载并激活Service Worker,降低因启动慢导致的提前请求:

<head>
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js', { scope: '/' })
        .then(registration => {
          // 若已有激活的Service Worker,直接控制当前页面
          if (registration.active) {
            registration.active.postMessage({ type: 'CLAIM' });
          }
        });
    }
  </script>
</head>

2. 修复请求头丢失问题,优化请求逻辑

原代码创建新Headers实例时会丢弃原请求的所有头信息,这既不符合预期,也可能加剧重复请求问题。正确做法是基于原请求头添加自定义字段:

function fetchWithHeader(request) {
  // 基于原请求headers创建实例,保留所有原有头信息
  const newHeaders = new Headers(request.headers);
  newHeaders.set('X-Custom-Header', '123');

  // 克隆原请求并替换headers,避免修改原请求引发异常
  const modifiedRequest = new Request(request, {
    headers: newHeaders
  });

  return fetch(modifiedRequest);
}

同时可添加缓存优先策略,Service Worker启动后直接从缓存返回资源,避免重复向后端发起请求:

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(cachedResponse => {
        // 缓存有资源则直接返回
        if (cachedResponse) {
          return cachedResponse;
        }
        // 无缓存则发起请求并缓存结果
        return fetchWithHeader(event.request).then(networkResponse => {
          caches.open('static-cache').then(cache => {
            cache.put(event.request, networkResponse.clone());
          });
          return networkResponse;
        });
      })
  );
});

3. 启用NavigationPreload(Safari 15.4+支持)

开启NavigationPreload可让Service Worker在启动过程中预加载页面资源,避免重复请求:

self.addEventListener('activate', event => {
  event.waitUntil(
    Promise.all([
      self.clients.claim(),
      // 启用NavigationPreload
      self.registration.navigationPreload.enable()
    ])
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    // 优先使用预加载响应
    event.preloadResponse.then(preloadResponse => {
      if (preloadResponse) {
        // 给预加载响应添加自定义头
        const modifiedResponse = new Response(preloadResponse.body, {
          headers: new Headers(preloadResponse.headers)
        });
        modifiedResponse.headers.set('X-Custom-Header', '123');
        return modifiedResponse;
      }
      // 回退到常规请求逻辑
      return fetchWithHeader(event.request);
    })
  );
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:42:41