TypeError: Load failed

Safari 17.1 Broke Your PWA Offline Mode — Here's the Fix

Safari 17.1 silently broke fetch events in service workers, killing offline PWAs. Fix your fetch handler or downgrade — full walkthrough below.

I know this one stings. You shipped a PWA that worked perfectly on Chrome, Edge, and every Android browser. Then someone on macOS Sonoma opens it in Safari, goes offline, and gets a blank screen. Safari 17.1 shipped in October 2023 with a change to how service workers handle fetch events, and it quietly nuked offline functionality for a lot of PWAs. The DevTools console gives you nothing useful — just TypeError: Load failed or a silent failure with no network request logged at all.

I spent two days of my life on this last November. Here's what's actually happening and how to fix it.

Cause 1: Your fetch handler uses async/await without responding to the event synchronously

This is the one that bites almost everyone. Safari 17.1 tightened up how it handles fetch events in service workers. If your handler looks like this, it's probably broken:

self.addEventListener('fetch', async (event) => {
  const cached = await caches.match(event.request);
  if (cached) return cached;
  return fetch(event.request);
});

On Chrome this works. On Safari 17.1, the event can complete before your async work resolves, and Safari drops the response. That's why you see a blank page offline instead of your cached shell.

The fix is to call event.respondWith() synchronously, then do your async work inside:

self.addEventListener('fetch', (event) => {
  event.respondWith((async () => {
    const cached = await caches.match(event.request);
    if (cached) return cached;
    try {
      return await fetch(event.request);
    } catch (e) {
      return caches.match('/offline.html');
    }
  })());
});

Notice the handler itself is no longer async. The outer function runs synchronously, respondWith gets called before the event lifecycle ends, and the promise resolves later. That's the pattern Safari 17.1 wants. If you take nothing else from this article, take this — it's the fix for roughly 70% of the reports I've seen.

Cause 2: You're caching responses with mismatched Vary headers or opaque responses

Safari 17.1 also changed how it treats opaque responses (those from cross-origin requests without CORS) in the Cache API. If you're caching CDN assets, fonts from Google, or analytics scripts without proper CORS headers, Safari may serve a corrupted or empty response offline even though the cache entry exists.

You'll spot this by checking Application → Cache Storage in Safari's Web Inspector. The entry is there. The size looks right. But await cached.text() returns an empty string.

Two options. First, add crossorigin="anonymous" to your cross-origin <script> and <link> tags and make sure the server returns proper Access-Control-Allow-Origin headers. Then you get real responses, not opaque ones.

Second, if you can't control the origin, skip caching opaque responses entirely:

caches.open('v1').then((cache) => {
  cache.addAll([
    '/',
    '/index.html',
    '/app.js',
    '/styles.css'
    // don't add third-party URLs here
  ]);
});

Let those requests hit the network. Your app still works offline for the shell and core assets, which is what matters. I know it feels wrong to give up on caching fonts, but a broken cache is worse than no cache.

Cause 3: Service worker registered with a scope mismatch after the 17.1 update

Less common but nasty. If your PWA was installed before the Safari 17.1 update and your service worker scope was something like /app/, Safari 17.1 may fail to match requests against the existing registration. You'll see fetch events fire in DevTools but with an empty event.request.url, or the handler never fires at all for in-scope URLs.

The annoying part: reinstalling the PWA doesn't always help because Safari caches the registration. Force it by bumping your service worker file:

  1. Change one byte in your sw.js file (add a comment).
  2. In Safari, go to Develop → Service Workers and click Unregister for your origin.
  3. Quit Safari completely — Cmd+Q, not just closing the window.
  4. Delete the installed PWA from /Applications if it's a macOS Sonoma web app.
  5. Reinstall from Safari, reopen, and check the console.

After that, verify the scope in the console:

navigator.serviceWorker.getRegistration().then(r => console.log(r.scope));

It should match the path where your WPA is installed. If it says https://yoursite.com/ and your app lives at https://yoursite.com/app/, register with an explicit scope:

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

Wait — is downgrading Safari an option?

No. Safari ships with the OS and you can't roll it back without reverting all of macOS Sonoma. Don't waste an evening trying. The three fixes above work, and once you patch the fetch handler pattern in Cause 1, you're probably done. The other two causes only matter if you're still seeing failures after that.

Safari's release notes for 17.1 mention "improved service worker reliability" — which is corporate-speak for "we changed the spec-compliance details and didn't tell anyone." The WebKit bug tracker has a few threads on this, but Apple hasn't shipped a fix as of 17.3.

Quick reference

SymptomCauseFix
Offline page blank, fetch event never fires or resolves empty Async fetch handler Call event.respondWith() synchronously, do async work inside the promise
Cache entry exists but response is empty offline Opaque responses / Vary headers Add crossorigin="anonymous" or stop caching cross-origin assets
Fetch handler fires with empty request URL, or not at all for in-scope routes Scope mismatch after 17.1 update Unregister SW, hard-quit Safari, reinstall PWA, set explicit scope on register()

Start with the fetch handler fix. It takes five minutes and it's almost certainly your problem. If you're still stuck after that, check the Cache Storage panel in Web Inspector and look at what's actually stored — that'll tell you fast whether you're in Cause 2 territory.

Related Errors in Software – Web Browsers
SSL_ERROR_RX_RECORD_TOO_LONG Firefox SSL_ERROR_RX_RECORD_TOO_LONG: Antivirus HTTPS Scanning Fix Fix Web Browser Crashes on Open: 5-Minute Flow Fix Browser Sending Multiple Requests on One Click PR_CONNECT_RESET_ERROR Fix Firefox PR_CONNECT_RESET_ERROR on specific sites

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.