Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Provide an offline fallback page

When the network is unavailable, users are shown a custom offline fallback page rather than the browser's generic error screen.

Utilities
Quick take
Typical fix time 20 min
  • Create a minimal /offline HTML page that works without any network requests
  • Pre-cache the offline page during the service worker install event
  • Intercept failed navigation requests and serve the offline page instead
  • Include helpful guidance so users know what to do while offline
Why it matters: The browser's default offline error screen ("No internet connection") is confusing and completely outside your brand. A custom offline page maintains the user experience, reinforces trust, and can surface cached content or useful actions โ€” such as enabling users to continue reading a cached article or queuing a form submission for later.

Rule Details

When a user visits your site without a network connection, the browser normally shows its own generic error page. A custom offline fallback page (opens in new tab) keeps users within your experience, and a service worker (opens in new tab) is what lets you return it for failed navigations.

Code Example

<!-- public/offline.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>You're offline โ€” Acme App</title>
    <style>
      /* All styles must be inline โ€” no external stylesheets */
      *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
 
      body {
        font-family: system-ui, -apple-system, sans-serif;
        background: #f8fafc;
        color: #1e293b;
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 100vh;
        padding: 1.5rem;
      }
 
      .card {
        background: #fff;
        border: 1px solid #e2e8f0;
        border-radius: 1rem;
        padding: 2.5rem 2rem;
        max-width: 420px;
        width: 100%;
        text-align: center;
      }
 
      .icon { font-size: 3rem; margin-bottom: 1rem; }
 
      h1 { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; }
 
      p  { color: #64748b; line-height: 1.6; margin-bottom: 1.5rem; }
 
      button {
        background: #3b82f6;
        color: #fff;
        border: none;
        border-radius: 0.5rem;
        padding: 0.625rem 1.5rem;
        font-size: 0.95rem;
        cursor: pointer;
        transition: background 0.15s;
      }
 
      button:hover  { background: #2563eb; }
      button:active { background: #1d4ed8; }
    </style>
  </head>
  <body>
    <div class="card">
      <div class="icon" aria-hidden="true">๐Ÿ“ก</div>
      <h1>You're offline</h1>
      <p>
        It looks like you've lost your internet connection. Check your network
        settings and try again.
      </p>
      <button onclick="window.location.reload()">Try again</button>
    </div>
  </body>
</html>

Why It Matters

The browser's default offline error screen ("No internet connection") is confusing and completely outside your brand. A custom offline page maintains the user experience, reinforces trust, and can surface cached content or useful actions โ€” such as enabling users to continue reading a cached article or queuing a form submission for later.

What the Offline Page Must Do

  • Load without any network requests โ€” all CSS, JavaScript, and images must be inline or already cached
  • Inform the user โ€” clearly explain they are offline
  • Offer actionable options โ€” a retry button, list of cached pages, or navigation to cached content
  • Match your brand โ€” consistent logo, fonts (pre-cached or system fonts), and colour scheme

Step 2: Pre-cache the Offline Page

Add /offline (or /offline.html) to the list of URLs cached during the service worker install event:

// public/sw.js
 
const CACHE_NAME = 'static-v1'
 
const PRECACHE_URLS = [
  '/',
  '/offline',          // โ† the offline fallback page
  '/styles/main.css',
  '/scripts/app.js',
]
 
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches
      .open(CACHE_NAME)
      .then((cache) => cache.addAll(PRECACHE_URLS))
      .then(() => self.skipWaiting())
  )
})

Step 3: Serve the Fallback on Navigation Failure

In your fetch handler, catch network errors for navigation requests and return the cached offline page:

// public/sw.js (fetch handler)
 
self.addEventListener('fetch', (event) => {
  // Only handle same-origin GET requests
  if (
    event.request.method !== 'GET' ||
    !event.request.url.startsWith(self.location.origin)
  ) {
    return
  }
 
  if (event.request.mode === 'navigate') {
    event.respondWith(handleNavigationRequest(event.request))
  }
})
 
async function handleNavigationRequest(request) {
  try {
    // Always try the network first for navigation
    const networkResponse = await fetch(request)
 
    // Cache a copy for later
    const cache = await caches.open(CACHE_NAME)
    cache.put(request, networkResponse.clone())
 
    return networkResponse
  } catch {
    // Network failed โ€” serve cached page if available, otherwise offline page
    const cached = await caches.match(request)
    if (cached) return cached
 
    const offlinePage = await caches.match('/offline')
    return (
      offlinePage ??
      new Response('<h1>Offline</h1>', {
        headers: { 'Content-Type': 'text/html' },
      })
    )
  }
}

Detecting Online/Offline State in the UI

You can also enhance the live page with an online/offline banner:

// Notify users when connection is lost or restored
function setupConnectivityBanner() {
  const banner = document.createElement('div')
  banner.setAttribute('role', 'status')
  banner.setAttribute('aria-live', 'polite')
  banner.style.cssText = `
    position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%);
    background: #1e293b; color: #fff; padding: 0.5rem 1.25rem;
    border-radius: 2rem; font-size: 0.875rem; display: none; z-index: 9999;
  `
  document.body.appendChild(banner)
 
  function showBanner(message: string) {
    banner.textContent = message
    banner.style.display = 'block'
  }
 
  function hideBanner() {
    banner.style.display = 'none'
  }
 
  window.addEventListener('offline', () => showBanner('You are offline'))
  window.addEventListener('online', () => {
    showBanner('Back online')
    setTimeout(hideBanner, 3000)
  })
}
 
if (typeof window !== 'undefined') {
  setupConnectivityBanner()
}

Using Workbox

If you use Workbox (opens in new tab), the offlineFallback plugin handles this automatically:

// sw.ts
import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute, setCatchHandler } from 'workbox-routing'
import { NetworkFirst } from 'workbox-strategies'
 
declare const self: ServiceWorkerGlobalScope & { __WB_MANIFEST: unknown[] }
precacheAndRoute(self.__WB_MANIFEST)  // /offline must be in the manifest
 
registerRoute(
  ({ request }) => request.mode === 'navigate',
  new NetworkFirst({ cacheName: 'pages' })
)
 
// Catch all failed navigation requests
setCatchHandler(async ({ request }) => {
  if (request.destination === 'document') {
    return (await caches.match('/offline'))!
  }
  return Response.error()
})
The offline page must be truly self-contained

Any external resource referenced in the offline page (fonts, images, scripts) must itself be pre-cached or inlined. If the offline page makes network requests that fail, the browser may display a blank or broken page โ€” which is worse than the browser's native error screen.

Support Notes

  • Offline behavior depends on actual browser support for service workers, cache storage, and installability, so validate on supported browsers and not only in one dev environment.
  • Document the graceful fallback for unsupported browsers explicitly.

Verification

Automated Checks

  • Register your service worker and confirm it is active in DevTools โ†’ Application โ†’ Service Workers.
  • Set the Network panel to Offline, then navigate to a page not in the cache โ€” you should see your custom offline page.
  • Run a Lighthouse PWA audit and confirm the "Responds with a 200 when offline" check passes.

Manual Checks

  • Confirm /offline appears in Cache Storage under your cache name.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Check whether the site shows a custom offline fallback page when the network is disconnected and a navigation request cannot be fulfilled.

Fix

Auto-fix issues

Create a self-contained /offline page and configure the service worker to pre-cache it and serve it for failed navigation requests.

Explain

Learn more

Explain how a service worker can intercept failed network requests and return a cached offline fallback page to the user.

Review

Code review

Review the service worker fetch handler and the offline page markup. Verify the offline page is pre-cached at install time, that it does not depend on external resources, and that the fallback is only served for navigation requests (not sub-resources).

Sources

References used to support the guidance in this rule.

Rules that often go hand-in-hand with this one.

Register a service worker for caching and offline support

A service worker is registered to intercept network requests, cache critical assets, and enable offline functionality for your web application.

Performance
Show loading indicators

Loading indicators provide feedback during asynchronous operations to keep users informed of progress.

Performance
Use fetchpriority to hint resource loading priority

The fetchpriority attribute is applied to critical images, scripts, and preload links to help the browser prioritise the most important resources and defer lower-priority ones.

Performance
Load non-critical code on user interaction

Defer JavaScript modules, widgets, and third-party code until the user signals intent through a click, focus, hover, or similar interaction.

Performance

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385