From c9e3caff5a325b6ab07da6de3e584e9e6ea5d333 Mon Sep 17 00:00:00 2001 From: robin Date: Sat, 20 Dec 2025 23:07:00 +0100 Subject: [PATCH] feat: implement ironclad offline strategy with pre-caching and root fallback --- public/sw.js | 53 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/public/sw.js b/public/sw.js index fbbe611..26f2401 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,13 @@ -const CACHE_NAME = 'whisky-vault-v3'; // Increment version to apply new strategy -const ASSETS_TO_CACHE = [ +const CACHE_NAME = 'whisky-vault-v4'; // Increment version for "Ironclad" strategy + +// CONFIG: Core pages and assets to pre-cache immediately on install +const CORE_PAGES = [ + '/', // Dashboard / Home + '/tasting/new', // Critical: Add Tasting Screen + '/scan', // Critical: Scan Screen +]; + +const STATIC_ASSETS = [ '/manifest.json', '/icon-192.png', '/icon-512.png', @@ -9,9 +17,12 @@ const ASSETS_TO_CACHE = [ self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { - return cache.addAll(ASSETS_TO_CACHE); + console.log('⚡ PWA: Pre-caching core pages and assets...'); + // Combine items to cache + return cache.addAll([...CORE_PAGES, ...STATIC_ASSETS]); }) ); + self.skipWaiting(); }); self.addEventListener('activate', (event) => { @@ -63,7 +74,24 @@ self.addEventListener('fetch', (event) => { return; } - // 1. ASSETS & APP SHELL: Stale-While-Revalidate + // 1. NEXT.JS DATA (RSC): Stale-While-Revalidate with empty JSON fallback + if (url.pathname.startsWith('/_next/data/')) { + event.respondWith( + fetchWithTimeout(event.request, 2000) + .catch(async () => { + const cachedResponse = await caches.match(event.request); + if (cachedResponse) return cachedResponse; + + // Fallback to empty JSON to prevent "Application Error" screens + return new Response(JSON.stringify({}), { + headers: { 'Content-Type': 'application/json' } + }); + }) + ); + return; + } + + // 2. ASSETS & APP SHELL: Stale-While-Revalidate const isAsset = event.request.destination === 'style' || event.request.destination === 'script' || event.request.destination === 'worker' || @@ -89,18 +117,21 @@ self.addEventListener('fetch', (event) => { return; } - // 2. NAVIGATION: Network-First with 3s Timeout + // 3. NAVIGATION: Ironclad Navigation Fallback if (event.request.mode === 'navigate') { event.respondWith( fetchWithTimeout(event.request, 3000) - .catch(() => { - console.log('[SW] Navigation network failure or timeout, falling back to cache'); - return caches.match(event.request); + .catch(async () => { + console.log('[SW] Navigation network failure, attempting cache fallback'); + const cachedResponse = await caches.match(event.request); + if (cachedResponse) return cachedResponse; + + // CRITICAL FALLBACK: Load the Root App Shell ('/') + // This allows Next.js to bootstrap and handle the routing client-side + console.warn('⚠️ PWA: Route not in cache, fallback to Root App Shell'); + return caches.match('/'); }) ); return; } - - // Default: Network-Only or default fetch - return; });