Offline App Shell

Start by caching only the shell needed to show a useful page. Add runtime caching after you understand your data freshness rules.

1. Add An Offline Page

<main>
  <h1>You are offline</h1>
  <p>Open the app again when your connection returns. Saved drafts remain on this device.</p>
</main>

2. Cache Shell Assets

const CACHE_NAME = 'shell-v1';
const SHELL_ASSETS = ['/', '/offline.html', '/styles.css', '/src/main.js'];

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

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
    )
  );
});

3. Return Fallback For Navigation

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

  event.respondWith(
    fetch(event.request).catch(async () => {
      const cache = await caches.open(CACHE_NAME);
      return cache.match('/offline.html');
    })
  );
});

Gotcha: This example assumes stable asset URLs. Real bundlers usually fingerprint JS and CSS. Use a build-integrated service worker or Workbox when asset lists are generated.