Summary
A Progressive Web App adds installability (Web App Manifest + icons) and offline capability (service worker caching) on top of a normal web site. The service worker intercepts fetch events and serves cached responses when the network is unavailable. Workbox or Serwist handle the boilerplate; IndexedDB stores structured data offline.
Jump to the interview angleProgressive Web App (PWA)
A web app that meets Chrome's installability criteria — a valid Web App Manifest with a 192 × 192 icon, an HTTPS origin, and a registered service worker — so the browser offers an 'Add to Home Screen' prompt. The service worker (SW) is a background JS thread that intercepts all outgoing fetches, enabling offline responses, push notifications, and background sync. A manifest alone doesn't make a site offline-capable; the SW's fetch handler does.
Service worker lifecycle
- 1
Register
The page calls
navigator.serviceWorker.register('/sw.js'). The browser downloads the SW file and begins the install phase. An existing active SW continues serving the page until the new one takes control. - 2
Install
The SW's
installevent fires. Pre-cache shell assets here withcache.addAll(urls). Callevent.waitUntil()to delay activation until caching succeeds. Failure rolls back to the previous SW. - 3
Activate
Fires after install succeeds and no old SW is controlling pages. Delete stale caches here. Call
self.clients.claim()to immediately take control of open tabs without waiting for a reload. - 4
Fetch
Every network request from controlled pages hits the SW's
fetchevent. Respond withevent.respondWith(strategy(request))— return a cached response, a network response, or a fallback. - 5
Update
The browser checks the SW file on every navigation. A byte-changed file triggers a new install. The new SW waits in
waitingstate unlessself.skipWaiting()is called, which forces immediate activation.
SW caching strategies
| Strategy | Response source | Network hit? | Best for | |
|---|---|---|---|---|
| Cache first | Cache; falls back to network and updates cache | Only on cache miss | Versioned static assets, fonts, icons | |
| Network first | Network; falls back to cache on failure | Always attempted | API responses that must be fresh when online | |
| Stale-while-revalidate | Cache immediately; fetches network in background to refresh cache | Yes, in background | Non-critical assets where speed matters more than freshness | |
| Network only | Network only; no cache read or write | Always | Analytics pings, POST mutations | |
| Cache only | Cache only; fails if not cached | Never | Pre-cached shell assets with no fallback needed |
Serwist fetch handler with stale-while-revalidate
Serwist (the Workbox fork maintained by @serwist/sw) ships typed strategy classes. Register routes in the SW entry point — Vite and Next.js plugins auto-inject the precache manifest.
// sw.ts — compiled by Serwist's Vite/Next plugin
import { Serwist } from "serwist";
import { StaleWhileRevalidate, CacheFirst } from "serwist";
import { ExpirationPlugin } from "serwist";
const serwist = new Serwist({
precacheEntries: self.__SW_MANIFEST, // injected at build time
skipWaiting: true,
clientsClaim: true,
navigationPreload: true,
});
// Versioned JS/CSS bundles: cache-first, auto-expire after 30 days
serwist.registerRoute(
({ request }) => request.destination === "script" || request.destination === "style",
new CacheFirst({
cacheName: "static-assets",
plugins: [new ExpirationPlugin({ maxAgeSeconds: 60 * 60 * 24 * 30 })],
}),
);
// HTML navigation: stale-while-revalidate so shell loads fast
serwist.registerRoute(
({ request }) => request.mode === "navigate",
new StaleWhileRevalidate({ cacheName: "pages" }),
);
serwist.addEventListeners();ExpirationPlugin auto-purges old entries so the cache doesn't grow unbounded. navigationPreload sends the network request in parallel with SW boot, cutting response time on fast connections.
skipWaiting races with open tabs
Calling skipWaiting forces the new SW to activate immediately, but tabs still open on the old version now run new SW code against old page assets — a version mismatch. The safe pattern: prompt the user ('Update available — reload?') and call skipWaiting only after they confirm, then reload all clients with clients.matchAll() + client.navigate(client.url).
Interview angle
Interviewers probe caching strategy selection and the update hazard from skipWaiting. Know when each strategy fits and what clientsClaim does. Soundbite: "Cache-first maximises speed but serves stale content; stale-while-revalidate gives speed without stale risk for assets that tolerate a one-request lag."
Key terms
- Service worker
- Background JS thread that intercepts fetches and enables offline responses and push notifications.
- Web App Manifest
- JSON file declaring app name, icons, display mode, and start URL, required for installability.
- Cache Storage API
- Browser API for storing Request/Response pairs; the SW reads and writes this for caching strategies.
- skipWaiting
- SW call that forces the waiting SW to activate immediately, bypassing the waiting state.
- IndexedDB
- Browser key-value object store for structured offline data; survives SW restarts unlike in-memory state.