HTML service worker: make one reference page available offline

A technician opens a wiring reference in the workshop, then loses the connection beside the equipment. A service worker can serve a previously cached copy of that reference when the network request fails. The page must first load from a supported secure origin, and the worker must control it before the offline test means anything.

For an HTML page, register a separate JavaScript worker, cache the specific reference during installation, and intercept only the requests your offline policy covers. Registration alone does not cache the page. MDN's service worker lifecycle guide explains the installation, activation, and control stages.

Give the reference a deliberately small scope

Suppose the deployed files are:

/field-guide/index.html
/field-guide/sw.js

Keep the reference self-contained for this first implementation: its CSS and diagrams live inside index.html. Add this registration code to that page:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register(
        './sw.js', { scope: './' }
      );
      console.log('Registered scope:', registration.scope);
      console.log('Controlled now:', !!navigator.serviceWorker.controller);
    } catch (error) {
      console.error('Reference registration failed:', error);
    }
  });
}

Serve it over HTTPS, or use http://localhost for development. Double-clicking the HTML file is unsuitable for this test. The worker script needs a valid JavaScript media type and a permitted same-origin scope. The default scope is the directory containing that script; see the register reference.

This example assumes the two deployed paths above. If your application uses a different base path, change the registration and the cached document together.

Cache a reference, then use it only when its navigation fails

Put this illustrative implementation in sw.js:

const CACHE = 'field-reference-v1';
const DOCUMENT = '/field-guide/index.html';

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE).then(cache => cache.add(DOCUMENT))
  );
});

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.filter(key =>
        key.startsWith('field-reference-') && key !== CACHE
      ).map(key => caches.delete(key))
    ))
  );
});

self.addEventListener('fetch', event => {
  const request = event.request;
  const url = new URL(request.url);
  if (request.method !== 'GET' || request.mode !== 'navigate' ||
      url.origin !== self.location.origin ||
      url.pathname !== DOCUMENT || url.search) return;

  event.respondWith((async () => {
    try {
      return await fetch(request);
    } catch {
      const cached = await caches.match(DOCUMENT, { cacheName: CACHE });
      return cached || new Response('Reference unavailable offline.', {
        status: 503,
        headers: { 'Content-Type': 'text/plain; charset=utf-8' }
      });
    }
  })());
});

This is a network-first navigation with a fixed installation-time fallback. It does not continually refresh the cached copy. An HTTP error response such as a server's 404 is still a completed network response, so this code returns it rather than silently substituting an older reference.

The exact-path check also matters. Visiting /field-guide/, adding a query string, or opening another page does not match this policy. Use the documented /field-guide/index.html address for the test. Supporting more routes requires an explicit policy for those routes.

Test control and content separately

  1. Load the reference online and inspect the service worker registration in browser developer tools. Confirm installation succeeded and the named cache contains the document.
  2. Reload the reference. In the page console, check navigator.serviceWorker.controller. On a first installation, an already-open document commonly needs a new navigation before it is controlled.
  3. Use developer tools to take the network offline, then reload the exact document address. Check the actual wiring labels and revision shown in the page.
  4. Restore the network and reload. Confirm the server's current reference appears again.

These are checks to run in your deployed test environment, not reported results from the code above. A successful registration message and a visible offline page establish different things. If the page loads but the diagram disappears, the HTML was cached while a separate diagram request was not.

For the first version, the self-contained HTML approach reduces that dependency list. A Web Worker solves a separate problem: running computation without blocking the page.

Plan how an older reference leaves the device

When the reference changes, update the worker file and its cache version, for example to field-reference-v2. The new installation can cache the new document while the old worker still controls open pages. Under the default lifecycle, the replacement waits until the old worker's clients are gone. This example deliberately leaves that lifecycle intact.

Close the reference's tabs, open it online again, and inspect which worker and cache are active before repeating the offline check. Avoid adding skipWaiting() simply to silence a waiting state: an immediate replacement needs a plan for pages that were loaded under the previous version.

Only cache material appropriate to retain on the device. This small public-reference pattern does not define authentication, logout cleanup, or a policy for private account pages. Browser storage can also be cleared or evicted, so keep an offline failure message and a way to reacquire the reference.

Before the technician relies on it, put a visible revision identifier in the document and verify that identifier during the offline test. A page that opens successfully may still be the wrong revision for the equipment.

Keep reading