# Offline fallback for service workers

> How to serve a meaningful offline page when both the cache and the network are unavailable, using the service worker fetch event and a pre-cached fallback response.

**In one line:** An offline fallback is a pre-cached HTML page (or API response) that your service worker returns when a navigation request fails and no cached version of the requested URL exists — so users see something useful instead of a browser error screen.

## Why a fallback is necessary

When a Network First strategy fails (no network, nothing cached for that URL), the service worker can either:

1. Let the request fall through — the browser shows its own offline error page (a dead end for the user).
2. Return a pre-cached fallback — a branded, helpful response that tells the user they are offline.

A fallback does not replace per-URL caching; it is the safety net for URLs that have never been loaded or whose cache entry has expired.

## Pre-caching the fallback

Pre-cache the offline page during the `install` event so it is always available:

```js
const FALLBACK_URL = '/offline.html';
const CACHE_NAME = 'offline-fallback-v1';

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.add(FALLBACK_URL))
  );
});
```

For single-page apps, the fallback is usually `/index.html` (the app shell), already pre-cached. No separate offline page is needed if the shell itself renders a useful "you are offline" state.

## Serving the fallback

Return the fallback only for navigation requests that fail both network and cache:

```js
self.addEventListener('fetch', (event) => {
  if (event.request.mode !== 'navigate') return;

  event.respondWith(
    fetch(event.request).catch(() =>
      caches.match(event.request).then(
        (cached) =>
          cached ?? caches.open(CACHE_NAME).then((c) => c.match(FALLBACK_URL))
      )
    )
  );
});
```

This pattern:
1. Tries the network.
2. On failure, checks whether the exact URL is cached.
3. If still nothing, returns the generic offline fallback.

## Image and font fallbacks

For non-navigation requests you can serve a fallback SVG or placeholder instead of a network error:

```js
const IMAGE_FALLBACK = '/fallback-image.svg';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match(IMAGE_FALLBACK))
    );
  }
});
```

## API fallback responses

For JSON API requests, return a structured fallback rather than an HTML error page:

```js
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  if (url.pathname.startsWith('/api/')) {
    event.respondWith(
      fetch(event.request).catch(() =>
        new Response(JSON.stringify({ error: 'offline' }), {
          headers: { 'Content-Type': 'application/json' },
          status: 503,
        })
      )
    );
  }
});
```

## Using Workbox

Workbox's `setCatchHandler` is the idiomatic way to handle unmatched fetch failures:

```js
import { setCatchHandler, NavigationRoute, registerRoute } from 'workbox-routing';
import { precacheAndRoute, matchPrecache } from 'workbox-precaching';

// Pre-cache the fallback as part of your manifest
precacheAndRoute(self.__WB_MANIFEST); // includes /offline.html

// Return the offline page for any failed navigation
setCatchHandler(async ({ event }) => {
  if (event.request.destination === 'document') {
    return matchPrecache('/offline.html');
  }
  return Response.error();
});
```

## Designing the offline page

A good offline fallback page:

- **Clearly tells the user they are offline** — do not leave them guessing why content failed to load.
- **Remains functional without network** — avoid scripts that require API calls. The page must render purely from its pre-cached HTML.
- **Reflects your product's visual style** — not a generic browser error.
- **Links to cached content** where practical (e.g., "Pages you've visited recently").

## Practical checklist

- [ ] Pre-cache the fallback in `install` with `event.waitUntil(cache.add('/offline.html'))`.
- [ ] Only serve the generic fallback for navigation requests (`mode === 'navigate'`); subresource failures should fail silently or return typed fallbacks.
- [ ] Test the fallback by opening DevTools → Network → "Offline" and navigating to an uncached URL.
- [ ] For SPAs, verify the app shell itself handles the offline state gracefully without a separate offline page.
- [ ] Version the fallback cache name and clean it up in `activate` alongside other old caches.

## Cross-references

- [Caching strategies](/reference/service-worker/caching-strategies/) — Network First is the strategy most often paired with a fallback
- [The Cache API](/reference/service-worker/cache-api/) — `cache.add()` for pre-caching; `caches.match()` at request time
- [Workbox](/reference/service-worker/workbox/) — `setCatchHandler` and `matchPrecache`