Fix /set offline cache: save markers and page assets on first visit
Deploy / deploy (push) Successful in 29s

The worker isn't controlling the page on the first visit, so warm-up fetches
bypassed it and no images were cached. The page now writes markers, its own
JS/CSS/fonts and /set/ straight into the cache and only then shows
'Works offline'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
twotalesanimation
2026-10-06 09:14:33 +02:00
parent ca122ad723
commit e6658eae3d
2 changed files with 30 additions and 10 deletions
+29 -10
View File
@@ -8,6 +8,8 @@ type Showing = { src: string; label: string; detail: string; screen?: MarkerScre
const KIND_LABEL: Record<Kind, string> = { phone: "Phones", tablet: "Tablets", laptop: "Laptops", monitor: "Monitors" };
const MODE_KEY = "ttvfx-set-mode";
// Must match CACHE in public/set/sw.js
const OFFLINE_CACHE = "ttvfx-set-v1";
function detectScreen() {
const dpr = window.devicePixelRatio || 1;
@@ -139,18 +141,35 @@ export default function MarkerPicker({ screens, extras }: { screens: MarkerScree
if (!showing && document.fullscreenElement) document.exitFullscreen?.().catch(() => {});
}, [showing]);
// Offline: register the service worker, then warm its cache with every marker
// Offline: register the service worker, then put every marker plus this page's own
// scripts/styles/fonts straight into its cache. Writing from the page (rather than
// relying on the worker intercepting fetches) also covers the very first visit,
// before the worker controls the page.
useEffect(() => {
if (process.env.NODE_ENV !== "production" || !("serviceWorker" in navigator)) return;
navigator.serviceWorker
.register("/set/sw.js", { scope: "/set/" })
.then(async () => {
if (process.env.NODE_ENV !== "production" || !("serviceWorker" in navigator) || !("caches" in window)) return;
let cancelled = false;
(async () => {
try {
await navigator.serviceWorker.register("/set/sw.js", { scope: "/set/" });
await navigator.serviceWorker.ready;
if (cancelled) return;
setOffline("saving");
const urls = [...screens.flatMap((s) => [s.full, s.edge]), ...extras.map((e) => e.src)].filter(Boolean) as string[];
await Promise.all(urls.map((u) => fetch(u).catch(() => null)));
setOffline("ready");
})
.catch(() => {});
const markers = [...screens.flatMap((s) => [s.full, s.edge]), ...extras.map((e) => e.src)].filter(Boolean) as string[];
const assets = performance
.getEntriesByType("resource")
.map((e) => new URL(e.name))
.filter((u) => u.origin === location.origin && /^\/(_next\/|icon|apple-icon)/.test(u.pathname))
.map((u) => u.pathname + u.search);
const cache = await caches.open(OFFLINE_CACHE);
await cache.addAll(["/set/", ...new Set([...markers, ...assets])]);
if (!cancelled) setOffline("ready");
} catch {
if (!cancelled) setOffline("idle");
}
})();
return () => {
cancelled = true;
};
}, [screens, extras]);
const showScreen = (s: MarkerScreen) => {