// public/sw.js // Route Commerce Service Worker // Two-cache strategy: shell (cache-first) + data (stale-while-revalidate) const SHELL_CACHE = "rc-shell-v3"; const DATA_CACHE = "rc-data-v3"; const OFFLINE_URL = "/offline.html"; const SHELL_ASSETS = [ "/", "/manifest.json", "/favicon.svg", "/og-default.svg", "/offline.html", "/icons/icon-192x192.png", "/icons/icon-512x512.png", ]; self.addEventListener("install", (event) => { event.waitUntil( caches.open(SHELL_CACHE).then((cache) => cache.addAll(SHELL_ASSETS)) ); self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( Promise.all([ caches.keys().then((cacheNames) => Promise.all( cacheNames .filter((name) => name !== SHELL_CACHE && name !== DATA_CACHE) .map((name) => caches.delete(name)) ) ), self.clients.claim(), ]) ); }); self.addEventListener("fetch", (event) => { const req = event.request; if (req.method !== "GET") return; const url = new URL(req.url); if (url.origin !== self.location.origin) return; // Navigations → network-first, fall back to cache, fall back to offline page if (req.mode === "navigate") { event.respondWith( fetch(req) .then((res) => { const clone = res.clone(); caches.open(SHELL_CACHE).then((cache) => cache.put(req, clone)); return res; }) .catch(() => caches.match(req).then((cached) => cached || caches.match(OFFLINE_URL)) ) ); return; } // Static assets → cache-first if ( url.pathname.startsWith("/_next/static/") || url.pathname.startsWith("/icons/") || url.pathname.startsWith("/screenshots/") || url.pathname.endsWith(".svg") || url.pathname.endsWith(".woff2") ) { event.respondWith( caches.match(req).then((cached) => { if (cached) return cached; return fetch(req).then((res) => { const clone = res.clone(); if (res.status === 200) { caches.open(SHELL_CACHE).then((cache) => cache.put(req, clone)); } return res; }); }) ); return; } // API GETs (read-only data) → stale-while-revalidate if (url.pathname.startsWith("/api/")) { event.respondWith( caches.open(DATA_CACHE).then((cache) => cache.match(req).then((cached) => { const network = fetch(req) .then((res) => { if (res.status === 200) cache.put(req, res.clone()); return res; }) .catch(() => cached); return cached || network; }) ) ); return; } // Default: try network, fall back to cache event.respondWith( fetch(req).catch(() => caches.match(req)) ); });