diff --git a/public/sw.js b/public/sw.js index d27bb301..0b2d3e09 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,10 +1,26 @@ -// Bumped to v4. The v3 worker cached /_next/static/ and /assets/ cache-first -// under a hardcoded cache name that was not tied to the build, so a release -// left the browser replaying the previous release's chunks against the new -// HTML. That combination renders a blank page. The cache name is the version, -// so it must move whenever this behaviour changes. -const CACHE = "atom-v4"; -const API_CACHE = "atom-api-v4"; +// Retired service worker — this file no longer serves anything. +// +// The app used to register a worker that cached /_next/static/ and /assets/ +// cache-first under a cache name with no build id. A release could not +// invalidate it, so browsers replayed the previous release's chunks against +// the new HTML and React never hydrated, which is what left the Catalog +// Studio on a blank white page. It also bought nothing: nginx already sends +// those two prefixes as `max-age=31536000, immutable` and their filenames are +// content-hashed, so the HTTP cache already handles them correctly. +// +// This file must stay at /sw.js and keep its install/activate handlers. +// Simply deleting it would leave every existing registration alive and in +// control of the page, with the old caching still running. Instead this +// worker does the opposite of what it used to: it deletes every cache and +// unregisters itself, then reloads open clients so they stop being +// controlled by it. +// +// Nothing registers it any more (see src/app/layout.tsx), so this only ever +// runs for browsers that already have a worker installed. Once a visitor +// loads the site again it uninstalls itself and never comes back. +// +// The manifest is unrelated and untouched — it is generated by +// src/app/manifest.ts and keeps the site installable without a worker. self.addEventListener("install", () => self.skipWaiting()); @@ -12,64 +28,13 @@ self.addEventListener("activate", (event) => { event.waitUntil( caches .keys() - .then((keys) => - Promise.all( - keys - .filter((k) => k !== CACHE && k !== API_CACHE) - .map((k) => caches.delete(k)), - ), + .then((keys) => Promise.all(keys.map((key) => caches.delete(key)))) + .then(() => self.registration.unregister()) + .then(() => + self.clients.matchAll({ type: "window", includeUncontrolled: true }), ) - .then(() => self.clients.claim()), + .then((clients) => { + for (const client of clients) client.navigate(client.url); + }), ); }); - -// Static build output is NOT cached here on purpose. -// -// nginx already sends `/_next/static/` and `/assets/` with -// `max-age=31536000, immutable`. Those filenames are content-hashed, so the -// HTTP cache is both sufficient and safe: a changed file gets a new name. -// Wrapping them in a Cache Storage entry bought nothing and introduced a -// staleness bug the HTTP cache cannot have — the cache name had no build id, -// so a release kept serving the previous release's chunks. -// -// Letting these requests fall through also means a failed fetch surfaces as a -// normal network error the browser can retry, instead of a rejected -// respondWith() that silently drops the