Deployment Live, PWA Still Old? Audit Service Worker Updates Without Version Drift
Service workers can leave users between old and new release generations. Audit waiting/activation, skipWaiting, cache versions, update UX, and rollback.
Deployment Live, PWA Still Old? Audit Service Worker Updates Without Version Drift
The deployment succeeds. Monitoring shows the new release. A teammate opens the site in a fresh tab and sees the fix. An hour later, a customer reports the exact bug you just shipped away — and their browser is still serving an older interface, older JavaScript, or an older offline fallback.
With a service worker, a release is no longer just “old server” versus “new server.” You may also have an old active service worker, a newly installed worker waiting to activate, several long-lived tabs, and multiple cache generations.
That is not a browser defect. The current W3C draft dated September 17, 2026 still models installation, waiting, and activation as distinct phases. The operational question is therefore: How do HTML, JavaScript, the service worker, and the API remain compatible while clients move between release generations?
The service worker is part of the release process
A service worker can intercept requests and answer from Cache Storage or from the network. That puts it directly between an already-loaded application and your latest deployment.
When the browser discovers a new worker version, it installs it first. If pages are still controlled by the previous worker, the new version normally waits. MDN describes this lifecycle: once the old clients are no longer in the way, the waiting worker can activate normally.
That protects running sessions. The trade-off matters: “deployment complete” does not mean “every client is now running the new release.”
Four states support and engineering should distinguish
- Which app version is loaded in the document? HTML and JavaScript can remain old in a long-lived tab.
- Which service worker controls the document?
navigator.serviceWorker.controlleridentifies the current controller. - Is a newer worker already in
waiting? An update may be ready without being in use. - Which cache generation is serving requests? Old Cache Storage entries are not automatically removed just because a new worker installed.
If those states are invisible, a release problem quickly becomes “please clear your browser cache.” That is not an update strategy.
skipWaiting() is not a free instant-update switch
skipWaiting() lets a waiting worker move toward immediate activation. Combined with clients.claim(), the new worker can then take control of already-open pages.
That sounds attractive, but it can create a mixed generation. web.dev explicitly warns that an immediately activated worker can begin controlling a page that was loaded with the previous application version. Earlier fetches used worker A; later fetches can use worker B.
For example:
- Release A loads
app-A.jsand expects API shape A. - Release B installs a worker with different cache behavior.
skipWaiting()activates B immediately.- The open tab is still executing JavaScript A, but its next fetches now pass through worker B.
If both generations are compatible, that is fine. If not, you have created a defect that appears mainly in long-lived sessions.
The useful question is not “Can we activate immediately?” It is: Will an old client remain valid after the worker changes underneath it?
Three clean update models
1. Let the lifecycle wait
The new worker remains in waiting until old controlled clients are gone. This is robust when long sessions are acceptable and older server and asset versions remain supported long enough.
2. Offer an update and reload deliberately
The page detects updatefound or a waiting worker and shows something like “A new version is available.” Only after the user accepts does the page tell the worker to run skipWaiting(). When controllerchange fires, the application performs one controlled reload.
The document, JavaScript, and worker now move to the new generation together. This is particularly useful for dashboards, editors, and SaaS products where an unexpected reload can destroy work.
3. Take over immediately — with a compatibility contract
A product can deliberately use skipWaiting() plus clients.claim() automatically. If it does, the architecture must support old and new clients temporarily sharing the same API and cache layer.
That is a release contract, not a one-line worker optimization.
Cache versioning must match activation
web.dev notes that installing a new service worker does not automatically remove previously cached assets. Cache names, precache manifests, and cleanup therefore need an explicit versioning strategy.
A common pattern writes new assets to a new cache and removes obsolete caches during activate. But cleanup can also happen too early. If an old page later needs a release-A lazy chunk after the new worker has deleted all A caches, that page can suddenly fail.
Hashed asset filenames help multiple generations coexist. The deployment process should not destroy the previous asset generation at the same instant new HTML goes live.
Update found is not update active
ServiceWorkerRegistration.update() asks the browser to check the worker script. If a byte-different version is found, the update lifecycle begins. updatefound signals that a new installation has started.
For UI and observability, distinguish the stages:
- Update check started ≠ installed.
- Installed ≠ active.
- Waiting ≠ used by the current tab.
- Activated ≠ document already reloaded.
An “App is up to date” badge that collapses all of those stages can give false confidence.
Long-lived tabs are the real hard case
The most useful release test is not a clean incognito profile. The difficult case is a client opened before the deployment.
Test at least this sequence:
- Open release A and keep the session alive.
- Open a second tab or an installed PWA window.
- Deploy release B.
- Observe when
updatefoundappears and whether B enterswaiting. - Keep working in the old tab: navigate, call APIs, lazy-load code, upload files, and submit forms.
- Trigger the intended update flow.
- Verify exactly one controlled reload and no reload loop.
- Simulate going offline during the update.
- Verify old and new clients converge on the same release.
- Simulate rollback or a defective B deployment.
If a release works only in a fresh browser profile, the test is being too polite.
API compatibility belongs in the design
An older tab may continue executing JavaScript A while the server already exposes API B. PWA deployments therefore share a property with other distributed systems: multiple client versions can exist at the same time.
Breaking API changes should not assume every browser updates within seconds. Versioned endpoints, compatibility windows, feature flags, or a clearly detected minimum client version with an understandable reload path are much more resilient.
A service worker can accelerate update delivery. It cannot erase incompatible client/server contracts.
A server rollback is not automatically a client rollback
If release B is faulty and you redeploy A on the server, devices still retain service worker registrations and Cache Storage. A rollback plan should therefore answer:
- Which worker version controls existing clients?
- Which cache generations still exist?
- Are previous assets still available from the origin or CDN?
- Can the rolled-back server communicate with release-B clients?
- Do you need a corrective worker rather than only a Git revert?
- How will users know a reload is required?
Rollback is a release path in both directions, not merely a server-side action.
What Website-Pflichtencheck would inspect
A service-worker and PWA release audit can review:
- registration, scope, and the worker script actually served,
install,waiting,activate, andcontrollerchange,- use of
skipWaiting()andclients.claim(), - update discovery through
updatefoundand explicitupdate()checks, - cache naming, versioning, and cleanup,
- long-lived tabs during deployment,
- hashed assets and retention of previous generations,
- update UI and controlled reload behavior,
- offline behavior during a version transition,
- API compatibility across old and new clients,
- rollback and recovery paths.
The goal is not to force every user onto a release in the same millisecond. The goal is an operating model that can explain which generation a client is running, how it moves safely to the next one, and what happens when a deployment has to be reversed.
If “clear your cache and reload” is still the main support instruction for your PWA, the missing piece is probably a visible, tested update contract between browser and deployment.