How to Add Offline Fallback Pages with Workbox
This guide covers the resilience side of Service Worker Caching Strategies, within Advanced Caching Strategies & CDN Architecture. Without a service worker, a user who loses connectivity mid-browse sees the browser's error page — a dead end that usually ends the session. With a fallback, they see your branded offline page with cached content, a retry button and links to pages already available offline.
The performance dimension is in the details: how the fallback is triggered. A naive network-first strategy waits for the network request to fail before showing the fallback, which on a flaky connection can mean waiting 30 seconds or more. A strategy with a network timeout shows the fallback (or cached content) after a few seconds instead. And the fallback must be precached and tiny, so installing it costs almost nothing for the majority who never go offline.
Rapid Diagnosis
- Test offline in DevTools. Network panel → Offline, then navigate to a page not visited before. Do you see your page or the browser's error?
- Test a flaky network. Use a custom throttling profile with high latency (2000ms+) and observe how long navigation takes to show anything.
- Check precache size. Application → Cache Storage; the precache should be small (tens of kilobytes for a fallback page and its assets).
- Check image and font fallbacks. Offline pages often appear broken because their images or fonts were never cached.
Root Cause Analysis
1. No fallback route. Navigation failures propagate as network errors.
2. Network-first without a timeout. Requests on flaky connections hang until the browser gives up, which can take a long time.
3. Fallback depends on uncached assets. The offline page references CSS, fonts or images that are not precached, so it renders unstyled.
4. Over-large precache. Teams precache entire sections "for offline", costing every user bandwidth on every release.
Step-by-Step Resolution
1. Precache a small, self-contained offline page
// sw.js (Workbox)
import { precacheAndRoute } from 'workbox-precaching';
precacheAndRoute([
{ url: '/offline.html', revision: OFFLINE_REVISION }, // inline CSS, no web fonts, small SVG logo
...self.__WB_MANIFEST.filter((e) => e.url.startsWith('/assets/core/')),
]);
// trade-off: inlining styles into offline.html duplicates some CSS, but makes
// the fallback independent of other caches — the whole point of a fallback.
2. Route navigations network-first with a timeout
import { registerRoute, setCatchHandler } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';
import * as navigationPreload from 'workbox-navigation-preload';
navigationPreload.enable();
registerRoute(({ request }) => request.mode === 'navigate',
new NetworkFirst({ cacheName: 'pages', networkTimeoutSeconds: 3 }));
// trade-off: a 3s timeout serves cached (possibly outdated) pages to users on
// very slow but working connections. Raise it for content that must be fresh.
3. Catch failures and serve the fallback
setCatchHandler(async ({ request }) => {
if (request.destination === 'document') return caches.match('/offline.html', { ignoreSearch: true });
if (request.destination === 'image') return caches.match('/assets/core/placeholder.svg');
return Response.error();
});
// trade-off: a generic placeholder image can confuse users if many images fail;
// keep it neutral and small.
4. Add retry and "available offline" links
On the offline page, listen for the online event to retry automatically, and list pages already in the pages cache so users can continue browsing.
Verification
With DevTools offline, navigate to uncached pages (fallback appears instantly), cached pages (served from the pages cache) and images (placeholder). With slow-network throttling, confirm the fallback or cached page appears around the timeout rather than after the browser's own timeout. Check that the precache adds only a few kilobytes to first-visit transfer.
Worked Example: A Transit Information Site
A public transit site's users often lost signal underground. Without a service worker they saw the browser error page and abandoned the session. The team added a precached 9KB offline page listing recently viewed timetables (read from the pages cache), network-first navigation with a 2.5-second timeout, and automatic retry on reconnect. Sessions that encountered a connectivity drop and continued afterwards rose from 18% to 64%, and online users saw no change in LCP because navigation preload kept network-first navigations fast.
Keeping Offline Support Cheap for Online Users
Offline support must not tax the common case. Keep three costs in check: precache size (only the fallback and its minimal assets, never whole sections), worker startup (enable navigation preload so network-first navigations do not wait for boot — see enabling navigation preload in service workers), and storage growth (expiration plugins with maxEntries and maxAgeSeconds on runtime caches). Measure first-visit transfer and LCP for new visitors before and after adding the worker; both should be essentially unchanged.
Designing the Offline Page Itself
A good offline page is honest, useful and light. State plainly that the connection is unavailable, offer an automatic retry when connectivity returns, and list content that is available offline (pages in the runtime cache, saved items). Avoid making it look like an error the user caused. Keep it within a few kilobytes: inline the critical styles, use a small inline logo, rely on system fonts, and avoid JavaScript beyond the retry and the cached-pages list. It must render perfectly with nothing but what was precached, on the slowest device you support.
Common Mistakes
- Precaching large pages "for offline". Every user downloads them on every release.
- Fallback page with external dependencies. Fonts and stylesheets from other origins will not load offline.
- No timeout. Flaky networks then feel worse with the worker than without.
- Forgetting to update the fallback revision. Users keep an old offline page forever.
Edge Cases
Authenticated pages. Cached authenticated pages should be cleared on logout to avoid showing private data offline.
Forms. Form submissions offline need Background Sync or a queued retry; a fallback page alone does not save user input.
SPAs. Client-side navigations do not trigger navigation requests; handle offline states in the app's data layer as well.
Analytics. Offline sessions generate events that cannot be sent; Workbox's background sync plugin can queue analytics requests.
FAQ
Does an offline page affect SEO or first-visit performance?
No. The service worker installs after the first page load and only intercepts subsequent navigations. Crawlers do not use service workers.
Which timeout should I use?
Two to four seconds is typical — long enough for slow but working connections, short enough that users on dead connections see something quickly. Tune it with RUM data on navigation durations.
Can I show cached content instead of a generic offline page?
Yes — that is what the pages cache provides for previously visited pages. The generic page is the fallback for pages never visited.
Do I need Workbox for this?
No, but Workbox's routing, strategies and catch handler implement the pattern with little code and well-tested edge cases.
How do I keep the pages cache from growing forever?
Add the expiration plugin with maxEntries (for example 50) and maxAgeSeconds (for example 7 days) to the navigation strategy.
What about iOS?
Safari supports service workers and Cache Storage, with storage eviction under pressure and stricter limits for sites not added to the home screen. Keep caches small and treat offline support as best-effort.
Related
- Precaching vs runtime caching with Workbox — choosing what to precache.
- Invalidating service worker caches on deploy — keeping fallbacks current.
- Debugging service worker cache misses in production — when cached pages are not served.