Offline fallback for service workers
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
Section titled “Why a fallback is necessary”When a Network First strategy fails (no network, nothing cached for that URL), the service worker can either:
- Let the request fall through — the browser shows its own offline error page (a dead end for the user).
- 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
Section titled “Pre-caching the fallback”Pre-cache the offline page during the install event so it is always available:
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
Section titled “Serving the fallback”Return the fallback only for navigation requests that fail both network and cache:
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:
- Tries the network.
- On failure, checks whether the exact URL is cached.
- If still nothing, returns the generic offline fallback.
Image and font fallbacks
Section titled “Image and font fallbacks”For non-navigation requests you can serve a fallback SVG or placeholder instead of a network error:
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
Section titled “API fallback responses”For JSON API requests, return a structured fallback rather than an HTML error page:
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
Section titled “Using Workbox”Workbox’s setCatchHandler is the idiomatic way to handle unmatched fetch failures:
import { setCatchHandler, NavigationRoute, registerRoute } from 'workbox-routing';import { precacheAndRoute, matchPrecache } from 'workbox-precaching';
// Pre-cache the fallback as part of your manifestprecacheAndRoute(self.__WB_MANIFEST); // includes /offline.html
// Return the offline page for any failed navigationsetCatchHandler(async ({ event }) => { if (event.request.destination === 'document') { return matchPrecache('/offline.html'); } return Response.error();});Designing the offline page
Section titled “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
Section titled “Practical checklist”- Pre-cache the fallback in
installwithevent.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
activatealongside other old caches.
Cross-references
Section titled “Cross-references”- Caching strategies — Network First is the strategy most often paired with a fallback
- The Cache API —
cache.add()for pre-caching;caches.match()at request time - Workbox —
setCatchHandlerandmatchPrecache