installkite

Offline guide, booklet 4 of 8

Service worker offline page: a fallback that actually helps

When the network is gone, a visitor should see your site, not the browser's error. A service worker can serve a friendly offline page, and with a little care it can also send people to the pages they already have.

Scissors cut the string between the network cloud and a phone, and the app keeps flyingScissors cut the string between the network cloud and a phone, and the app keeps flying

Why an offline page matters

Without a service worker, a lost connection means the browser's own error screen, and in an installed app that looks broken. With one, every page navigation goes through your code first. You decide what to show: fresh content when the network answers, a cached copy when it does not, and a clear, branded page for the rest.

Precache the page during install

The offline page has to be stored while the network still works. Add it to the install step of your worker:

sw.js
const CACHE = 'pages-v3';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(['offline.html', './'])));
});

Keep the page self contained. If it links a stylesheet or an image, those must be cached too, and a missing one leaves the offline page unstyled exactly when it matters. Inline the CSS and put small images in data URLs.

Match folders and file names

Static hosts serve /menu/ from menu/index.html and often /menu from menu.html. If you precached menu/index.html, a navigation to /menu/ will not match it by default. Try the variants before giving up: the URL without its query string, the folder index, and the .html name. installkite's worker does this for every navigation.

Point people to what they have

An offline page that says only "You are offline" is a dead end. The page can read the Cache Storage API itself and list the pages already saved on the device:

offline.html (script)
const names = await caches.keys();
const urls = [];
for (const name of names) {
  for (const req of await (await caches.open(name)).keys()) {
    const path = new URL(req.url).pathname;
    if (path.endsWith('/') || path.endsWith('.html')) urls.push(path);
  }
}
document.querySelector('#saved').innerHTML = [...new Set(urls)].map((p) => `<a href="${p}">${p}</a>`).join('');
addEventListener('online', () => location.reload());

Reloading on the online event brings the visitor back to what they wanted the moment the connection returns.

The installkite offline page

npx installkite init writes offline.html in your theme and background colors, with your app icon inlined as a data URL, a Try again button, automatic reload when the connection returns and a list of the pages saved on the device. It is precached with a content hash, and the worker serves it for any navigation inside the scope that has no cached copy. The timeout is configurable:

installkite.config.json
"sw": {
  "offline": "offline.html",
  "navigationTimeout": 2500,
  "exclude": ["api/", "admin/"]
}

Run npx installkite init --yes again to apply config changes, and npx installkite doctor checks that the offline page exists and is precached.

What not to cache

Be careful with pages that depend on the visitor: a cart, an account page, anything behind a login. Storing them in a shared cache can show stale or private data after a sign out. Exclude those paths from the worker, and let API calls and form posts go straight to the network. installkite only handles GET requests, never touches requests with a Range header such as video seeking, and takes an exclude list of path prefixes or regular expressions in its config, as shown above. Also avoid caching opaque responses from other origins: browsers count each one as several megabytes against your storage quota.

Testing it

  1. Load the site once so the worker installs, then reload so it controls the page.
  2. In developer tools, Network panel, switch to Offline.
  3. Open a page you visited: it loads from the cache. Open one you never visited: the offline page appears.
  4. Switch back online: the offline page reloads itself.

To start fresh, use Application, Storage, Clear site data. On a phone, airplane mode is the honest test.

Questions

Why does my offline page not show?

The offline page must be in the cache before the network goes away, so precache it during the install event. It also only applies to navigations the worker controls: pages inside its scope, loaded after the worker activated.

Can the offline page load CSS and images?

Only what is cached. The safest offline page is self contained: inline styles and an inlined icon, which is what installkite generates.

How do I test the offline page?

In Chrome or Edge open the developer tools, Network panel, set Offline, then open a page you never visited. The Application panel lists the caches and lets you unregister the worker to start again.

Does the offline page work on iPhone?

Yes. Safari on iOS supports service workers, both in the browser and in sites installed to the home screen.

Suggestions

Nothing matches. Skye looked behind every cloud.