← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / Service Workers & PWA

Service Workers & PWA

Offline web apps, background sync, push notifications, and the Progressive Web App model.

What it is, in plain English: A Service Worker is a script that runs in the background, separate from your web page, acting as a programmable network proxy. It can intercept every network request your page makes and respond from cache — making your app work offline. Service Workers are the foundation of Progressive Web Apps (PWA): installable web apps that work offline, send push notifications, and feel like native apps.
Where this bites you

With the cache-first fetch handler above, once a file is in the cache, the cache wins: forever. Deploy a new app.js and every returning visitor keeps getting the old one, because the service worker answers before the network is ever asked. Nothing errors, and it usually looks fine on the developer's machine, where DevTools is often set to bypass the service worker.

// sw.js -- deployed with release 1
const CACHE = "my-app-v1";
// ... install caches "/app.js"; fetch answers from the cache first ...

// release 2 changes app.js -- but sw.js is left untouched
// app.js v1:  console.log("app v1");
// app.js v2:  console.log("app v2");

// returning visitor, reload after release 2:
// app v1
// reload again:
// app v1          <- the new app.js is never fetched

Making web apps work offline

A Service Worker is a script that sits between your web page and the network. It can catch network requests and serve cached responses — so your app works even when there's no internet connection. It also enables push notifications and background sync.

Browser support note: Service Workers require HTTPS (or localhost for development). They don't work on http:// pages in production.

Step 1 — Register the Service Worker

// In your main JavaScript file (app.js or index.js):
if ("serviceWorker" in navigator) {
  window.addEventListener("load", async () => {
    try {
      const reg = await navigator.serviceWorker.register("/sw.js");
      console.log("Service Worker registered:", reg.scope);
    } catch (error) {
      console.error("Registration failed:", error);
    }
  });
}

Step 2 — The Service Worker file (sw.js)

// sw.js — lives at the root of your site

const CACHE = "my-app-v1";
const ASSETS = ["/", "/index.html", "/style.css", "/app.js"];

// install event — cache core files when SW is first installed:
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE).then(cache => cache.addAll(ASSETS))
  );
  self.skipWaiting();  // activate immediately (don't wait for old SW)
});

// activate event — clean up old caches:
self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(
        keys.filter(key => key !== CACHE)
            .map(key  => caches.delete(key))
      )
    )
  );
  self.clients.claim();  // take control of open tabs immediately
});

// fetch event — intercept every network request:
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      if (cached) return cached;  // serve from cache
      return fetch(event.request); // fall back to network
    })
  );
});

The activate handler above already deletes old caches, but it only runs when a new sw.js is installed, and the browser only installs one when the file's bytes change. So change the cache name with every release; build tools such as Workbox do this for you with a content hash. Expect one extra load: the visit that discovers the new worker is still served by the old one, and the next visit gets the new files.

// sw.js -- release 2
const CACHE = "my-app-v2";   // new name every release: sw.js changes, so it reinstalls

// visit 1 after release 2:  app v1   (served by the old worker while the new one installs)
//                           caches: my-app-v1, my-app-v2
// visit 2:                  app v2   (the new worker is active; activate deleted my-app-v1)
//                           caches: my-app-v2

Step 3 — Web App Manifest (for installability)

// manifest.json — makes your app installable ("Add to home screen"):
{
  "name": "My App",
  "short_name": "App",
  "start_url": "/",
  "display": "standalone",   // hides the browser UI
  "background_color": "#ffffff",
  "theme_color": "#2563eb",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

// Link in index.html:
// <link rel="manifest" href="/manifest.json">
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.