Offline Fallback Page
Serving an offline fallback page
Section titled “Serving an offline fallback page”The simplest improvement you can make to a PWA that has no offline strategy is adding a single offline fallback page. When a user navigates to your site and the network is unavailable, instead of the browser’s error screen, they see a branded, friendly page that tells them they are offline and provides helpful context.
The two-step pattern
Section titled “The two-step pattern”- On
install— precacheoffline.htmlso it is available immediately, even before the user has ever visited any other page. - On
fetch— intercept navigation requests (event.request.mode === 'navigate'), try the network, and catch failures by serving the cachedoffline.html.
This pattern is deliberately minimal. It does not intercept sub-resources (images, scripts, API calls) — only top-level page navigations. That keeps the logic simple and avoids unintended side effects on API calls.
Precaching on install
Section titled “Precaching on install”const OFFLINE_URL = '/offline.html';
self.addEventListener('install', (event) => { event.waitUntil( caches.open('offline-v1').then((cache) => cache.addAll([OFFLINE_URL])) ); self.skipWaiting();});event.waitUntil() keeps the install phase alive until the Promise resolves, ensuring the file is cached before the SW activates. self.skipWaiting() makes the new SW take control immediately without waiting for the old one to be released.
Intercepting navigation requests
Section titled “Intercepting navigation requests”self.addEventListener('fetch', (event) => { if (event.request.mode !== 'navigate') return;
event.respondWith( fetch(event.request).catch(() => caches.match('/offline.html')) );});event.request.mode === 'navigate' is true only for top-level navigation requests — clicking a link, entering a URL, or refreshing the page. Sub-resource requests (CSS, JS, XHR) have other modes ('cors', 'no-cors', 'same-origin'), so this guard ensures we only intercept page loads.
Activating and claiming clients
Section titled “Activating and claiming clients”self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim());});self.clients.claim() makes the service worker take control of already-open pages without requiring a reload. Without it, the current page is not controlled until the next navigation.
Navigation request flow
Section titled “Navigation request flow”sequenceDiagram
participant U as User
participant SW as Service Worker
participant N as Network
participant C as Cache Storage
U->>SW: navigate to /page (mode: navigate)
SW->>N: fetch(request)
alt Network available
N-->>SW: 200 OK Response
SW-->>U: Page renders normally
else Network unavailable
N--xSW: fetch() rejects
SW->>C: caches.match('/offline.html')
C-->>SW: cached offline.html
SW-->>U: Offline fallback page
end Try it live
Section titled “Try it live”The demo below registers a service worker that precaches offline.html. Open the StackBlitz preview, then use the browser’s DevTools Network tab to toggle offline mode and reload the page — you should see the offline fallback instead of the dinosaur.
What to put in your offline page
Section titled “What to put in your offline page”A good offline fallback page should:
- Match your brand — same fonts, colors, and logo as the rest of the app.
- Explain the situation clearly — “You are offline” is better than a generic error.
- Offer a retry button —
location.reload()is sufficient. - Be completely self-contained — no external stylesheets or scripts, because those will also fail offline.
- Stay small — under 5 KB compressed so it precaches instantly.