Glossary

Definition

waiting state (service worker)

Short definition

A service worker enters the waiting state after install succeeds but before it can activate, because the browser only replaces an active worker once every page it currently controls has been closed or navigated away from. It stays here indefinitely until those pages close or the code forces the transition with skipWaiting().

Reviewed July 27, 2026

This staged rollout exists to prevent a page from suddenly being served by mismatched worker logic mid-session — for example, an old page expecting API shape A should not suddenly be intercepted by a new worker that assumes shape B.

You can detect a waiting worker from the page with registration.waiting, commonly used to show a "New version available, refresh to update" banner rather than silently forcing a reload.

A common pattern posts a message to the waiting worker (worker.postMessage({type: "SKIP_WAITING"})), and the worker's own message handler calls self.skipWaiting() in response, keeping the decision to update in the user's or app's hands rather than happening automatically.

Leaving many tabs of the same PWA open indefinitely is the most common reason teams see "why isn't my update showing up" bug reports — each open tab keeps the old worker active and the new one waiting.

Related terms
skipWaiting()
self.skipWaiting() is called inside a service worker's install (or activate) handler to skip the normal waiting state and activate immediately, even while older pages controlled by a previous worker are still open. It is usually paired with clients.claim() so the new worker also starts controlling those pages right away.
Service worker update cycle
Browsers check a registered service worker's script for updates on every navigation to a page in scope, and at least every 24 hours in the background, by re-fetching the script URL and comparing it byte-for-byte against the currently installed version. Any difference — even a single changed character or comment — triggers a fresh install/waiting/activate cycle for a new worker instance.
Service worker registration
Registration happens when a page calls navigator.serviceWorker.register("/sw.js", { scope: "/app/" }). The scope option (defaulting to the directory the script is served from) determines which pages the worker is allowed to control — a worker at /sw.js served with no scope option can normally only control /*, but serving it with the Service-Worker-Allowed HTTP header lets it claim a scope above its own script location.
clients.claim()
self.clients.claim() is called inside the activate event of a service worker to make it start controlling every open page on the origin right away, rather than the default behaviour where a page keeps being controlled by whatever worker was active when it first loaded until the next navigation.
Cache Storage API
The Cache Storage API, exposed as the global caches object, lets a service worker (or page) store Request/Response pairs in named caches, e.g. caches.open("shell-v1").then((cache) => cache.put("/logo.png", response)). Unlike the HTTP cache, it is fully scriptable, has no automatic eviction schedule tied to headers, and persists until explicitly deleted or the storage quota is exceeded.
Cache-first strategy
Cache-first is a fetch-handling strategy: event.respondWith(caches.match(event.request).then((cached) => cached ?? fetch(event.request))). It serves cached content instantly and skips the network entirely when a match exists, making it ideal for versioned, immutable assets such as hashed JS/CSS bundles or app-shell icons that never change once deployed.