Service Workers & PWA
Offline web apps, background sync, push notifications, and the Progressive Web App model.
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 fetchedMaking 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.
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-v2Step 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">Official Sources
- MDN Web Docs — Service Worker API
- MDN Web Docs — Using Service Workers
- web.dev — Progressive Web Apps
- Workbox Documentation
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.