refactor(pwa): drop the unreachable precached-shell fallback

Without a connection a navigation also looked for itself in the static
cache, which only ever holds icons and catalogs; `/` is never
precached, and the install step deleted a leftover root document only
so that this lookup could not find it. Both go; the page cache and the
offline page remain.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 20:41:05 +01:00
1 parent 09f3280c1c
commit a824484d4a
2 files changed
+10 -45

No files matched your search

+10 -36
View File
@@ -74,13 +74,7 @@ self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open(STATIC_CACHE)
.then(async (cache) => {
// An updated worker can reuse the same release-scoped cache when its
// source changes without a version bump. Remove a root document left
// by the previous worker before adding the safe public asset set.
await cache.delete("/");
await cache.addAll(PRECACHE_URLS);
})
.then((cache) => cache.addAll(PRECACHE_URLS))
.then(() => self.skipWaiting()),
);
});
@@ -383,14 +377,11 @@ async function retireFromForeignOrigin() {
}
/**
* Network-first: try network (preferring the navigation-preload response
* when the browser already started it), fall back to cache.
*
* The offline fallback only serves a shell cached under the CURRENT
* `CACHE_VERSION` cache names. The previous global `caches.match()` could
* resolve from a stale pre-update cache in the activation gap and serve a
* shell whose `/_next/static/*` chunk graph no longer exists
* (`ChunkLoadError` on the first lazy navigation).
* Network-first for pages: the navigation-preload response when the browser
* already started it, else a fetch. Without a connection, a page from this
* release's page cache (only public pages are there), else the offline page.
* The lookup is scoped to this release's cache name: a page from an older
* release would reference chunks that no longer exist.
*/
async function networkFirst(event, cacheName) {
const { request } = event;
@@ -417,28 +408,8 @@ async function networkFirst(event, cacheName) {
}
return response;
} catch {
// Version-scoped fallback: the page cache first, then the precached
// app shell — both names embed CACHE_VERSION, so a stale shell from a
// previous release can never be served here.
const pageCache = await caches.open(cacheName);
const cachedPage = await pageCache.match(request);
const cachedPage = await (await caches.open(cacheName)).match(request);
if (cachedPage) return cachedPage;
const staticCache = await caches.open(STATIC_CACHE);
const precached = await staticCache.match(request);
if (precached) return precached;
// Offline fallback. F-7 (mobile security audit, 2026-05-16): the
// previous fallback rendered hard-coded German body copy, which is
// wrong for any en/* user reaching it offline. The replacement is
// language-neutral — wordmark + the universally understood
// "Offline" token + a generic retry hint expressed as an icon
// (the round-trip arrow), so the page does the right thing in any
// locale without shipping a translation bundle into the worker.
//
// v1.42 — the arrow is a real control now (a link to the same URL,
// 64 px), the page reloads by itself as soon as the connection is
// back, it follows the device's light or dark scheme, and it keeps
// clear of the notch and home indicator of an installed app.
return new Response(OFFLINE_PAGE, {
status: 503,
headers: { "Content-Type": "text/html; charset=utf-8" },
@@ -446,6 +417,9 @@ async function networkFirst(event, cacheName) {
}
}
// Language-neutral, because the worker ships no catalog: the wordmark, the
// word "Offline" and a 64 px retry link. It reloads itself when the
// connection returns, follows the device scheme and keeps clear of the notch.
const OFFLINE_PAGE =
'<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><meta name="color-scheme" content="dark light"><title>HealthLog — Offline</title><style>' +
":root{--bg:#282a36;--fg:#f8f8f2;--accent:#bd93f9;--muted:#a4a9c9;color-scheme:dark}" +
-9
View File
@@ -548,15 +548,6 @@ describe("sw.js — networkFirst offline fallback", () => {
expect(await res.text()).toBe("current shell");
});
it("falls back to the current-version precached shell when the page cache misses", async () => {
const harness = bootServiceWorker();
const statics = await harness.cacheStorage.open(CURRENT_STATIC_CACHE);
await statics.addAll(["/"]);
const res = await dispatchNavigationFetch(harness, "/");
expect(await res.text()).toBe("precached:/");
});
it("consumes the navigation-preload response instead of re-fetching", async () => {
const harness = bootServiceWorker();
let fetchCalls = 0;