feat: implement ironclad offline strategy with pre-caching and root fallback

This commit is contained in:
2025-12-20 23:07:00 +01:00
parent b7ac0957d1
commit c9e3caff5a

View File

@@ -1,5 +1,13 @@
const CACHE_NAME = 'whisky-vault-v3'; // Increment version to apply new strategy const CACHE_NAME = 'whisky-vault-v4'; // Increment version for "Ironclad" strategy
const ASSETS_TO_CACHE = [
// 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', '/manifest.json',
'/icon-192.png', '/icon-192.png',
'/icon-512.png', '/icon-512.png',
@@ -9,9 +17,12 @@ const ASSETS_TO_CACHE = [
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
event.waitUntil( event.waitUntil(
caches.open(CACHE_NAME).then((cache) => { 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) => { self.addEventListener('activate', (event) => {
@@ -63,7 +74,24 @@ self.addEventListener('fetch', (event) => {
return; 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' || const isAsset = event.request.destination === 'style' ||
event.request.destination === 'script' || event.request.destination === 'script' ||
event.request.destination === 'worker' || event.request.destination === 'worker' ||
@@ -89,18 +117,21 @@ self.addEventListener('fetch', (event) => {
return; return;
} }
// 2. NAVIGATION: Network-First with 3s Timeout // 3. NAVIGATION: Ironclad Navigation Fallback
if (event.request.mode === 'navigate') { if (event.request.mode === 'navigate') {
event.respondWith( event.respondWith(
fetchWithTimeout(event.request, 3000) fetchWithTimeout(event.request, 3000)
.catch(() => { .catch(async () => {
console.log('[SW] Navigation network failure or timeout, falling back to cache'); console.log('[SW] Navigation network failure, attempting cache fallback');
return caches.match(event.request); 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; return;
} }
// Default: Network-Only or default fetch
return;
}); });