SW: non-disruptive update — drop skipWaiting/claim (post-deploy boot stall)

Telemetry isolated the last boot-slow source (id9): warm edge, fast shell
(60ms), but chunks took 2.6-4.7s on the LAN-fast dev box, 26min post-deploy —
i.e. NOT network/origin. Cause: on the first load of a new build the new SW
ran skipWaiting()+clients.claim(), activating mid-boot — deleting the old
cache and seizing the loading page, yanking the cache from under the ~16
in-flight chunk requests (the sz0 + clustered-start + staggered-finish
signature).

Fix (Codex-approved): remove skipWaiting() and clients.claim() so a new worker
installs quietly and takes control on the NEXT navigation, never mid-boot. The
post-deploy first load then completes under the stable old worker (cache
intact) against the warmed edge. Cache cleanup stays in activate (now runs only
at the deferred, safe activation); old immutable chunks live 14 days at the
origin regardless, so a slightly-behind worker still loads safely. Trade-off —
SW/shell update applies one navigation later — is fine: the shell is
edge-cached and the SW's only job is offline/slow-network fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-12 07:52:34 -04:00
parent a34a47fe22
commit f84d934da5
+10 -4
View File
@@ -40,17 +40,23 @@ function isMutablePath(p) {
self.addEventListener('install', (event) => {
// Best-effort: grab the app shell as an offline fallback. No bulk precache.
event.waitUntil(
caches.open(CACHE).then((c) => c.add('/').catch(() => {})).then(() => self.skipWaiting())
);
// Deliberately NO skipWaiting(): a new worker installs quietly and waits, so
// it never seizes a page that's mid-boot. It takes control on the next
// navigation, when the old worker has no clients — the post-deploy first-load
// then completes under the stable old worker (with its cache intact) against
// the warmed edge, instead of having its cache yanked mid-load.
event.waitUntil(caches.open(CACHE).then((c) => c.add('/').catch(() => {})));
});
self.addEventListener('activate', (event) => {
// Runs only when this worker actually activates (next navigation, never
// mid-boot), so deleting old version caches here is safe — and old immutable
// chunks live on at the origin for a 14-day grace window regardless. No
// clients.claim(): pages adopt this worker on their own next navigation.
event.waitUntil(
caches
.keys()
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
.then(() => self.clients.claim())
);
});