diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 965692c..615a593 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,51 +1,50 @@ -// Service Worker – Network-first für HTML, kein Cache für Assets -// Stellt sicher dass nach einem Deploy immer der neue Stand geladen wird +// Service Worker – stellt sicher dass nach einem Deploy immer der neue Stand geladen wird +// index.html und API: immer vom Netzwerk (no-store) +// Assets (JS/CSS mit Hash): Network-first mit Cache-Fallback für Offline const CACHE_NAME = 'dickendock-v2'; self.addEventListener('install', () => { - self.skipWaiting(); // sofort aktivieren, nicht auf Tab-Schließen warten + self.skipWaiting(); // sofort aktivieren ohne auf Tab-Schließen zu warten }); self.addEventListener('activate', e => { - // Alle alten Caches löschen e.waitUntil( caches.keys() .then(keys => Promise.all(keys.map(k => caches.delete(k)))) - .then(() => self.clients.claim()) // alle offenen Tabs übernehmen + .then(() => self.clients.claim()) // alle offenen Tabs sofort übernehmen ); }); self.addEventListener('fetch', e => { const url = new URL(e.request.url); - // API-Requests immer direkt ans Netzwerk, nie cachen + // API-Requests: nie cachen if (url.pathname.startsWith('/api/')) { e.respondWith(fetch(e.request)); return; } - // index.html und Navigation: Network-first, kein Cache - // Damit nach einem Deploy immer die neue Version geladen wird - if (e.request.mode === 'navigate' || - url.pathname === '/' || - url.pathname === '/index.html') { + // HTML-Navigation (index.html, /, /*, alle Routen): immer frisch vom Server + // Das ist der Kern: verhindert dass eine alte index.html aus dem HTTP-Cache kommt + if (e.request.mode === 'navigate') { e.respondWith( - fetch(e.request, { cache: 'no-store' }) - .catch(() => caches.match('/index.html')) // Fallback wenn offline + fetch(e.request, { + cache: 'no-store', // Browser-HTTP-Cache umgehen + headers: { 'Cache-Control': 'no-cache' } + }).catch(() => caches.match('/index.html')) // nur bei echtem Offline-Fall: Cache-Fallback ); return; } - // JS/CSS/Assets: Network-first mit Cache-Fallback - // Vite baut mit Content-Hash im Dateinamen → neue Version = neue URL + // Statische Assets (Vite baut mit Content-Hash im Dateinamen): + // Network-first, bei Fehler Cache-Fallback (für Offline-Nutzung) e.respondWith( fetch(e.request) .then(response => { - // Erfolgreiche Antwort kurz cachen als Offline-Fallback if (response.ok) { const clone = response.clone(); - caches.open(CACHE_NAME).then(cache => cache.put(e.request, clone)); + caches.open(CACHE_NAME).then(c => c.put(e.request, clone)); } return response; }) @@ -53,7 +52,6 @@ self.addEventListener('fetch', e => { ); }); -// Auf Update-Nachricht vom Frontend reagieren self.addEventListener('message', e => { if (e.data === 'SKIP_WAITING') self.skipWaiting(); }); diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 84e380f..1f02f61 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client' import App from './App.jsx' // ── Auto-Reload bei neuem Build ─────────────────────────────────────────────── +// Reload passiert beim nächsten Seitenwechsel (setActive) — nicht mitten in Eingaben const STORED_KEY = 'dd_build_time'; let newBuildAvailable = false; @@ -20,11 +21,12 @@ async function checkBuildTime() { } catch {} } -// Alle 60s im Hintergrund prüfen +// Beim Start sofort prüfen (PWA-Launch: index.html könnte gecacht sein) checkBuildTime(); +// Danach alle 60s im Hintergrund weiter prüfen setInterval(checkBuildTime, 60_000); -// Reload-Funktion – wird vom App beim Seitenwechsel aufgerufen +// Wird von App.jsx bei jedem Seitenwechsel aufgerufen export function reloadIfNewBuild() { if (newBuildAvailable) window.location.reload(); } @@ -32,7 +34,7 @@ export function reloadIfNewBuild() { // ── Service Worker ──────────────────────────────────────────────────────────── if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(reg => { - // Falls schon ein wartender SW da ist → sofort aktivieren + // Falls schon ein wartender SW vorhanden → sofort aktivieren if (reg.waiting) { reg.waiting.postMessage('SKIP_WAITING'); } @@ -41,16 +43,16 @@ if ('serviceWorker' in navigator) { const nw = reg.installing; nw?.addEventListener('statechange', () => { if (nw.state === 'installed') { - // Neuer SW installiert → sofort aktivieren + // Neuer SW fertig installiert → sofort aktivieren nw.postMessage('SKIP_WAITING'); } }); }); - // Wenn SW-Controller gewechselt hat → sofort neu laden - // (nicht erst beim nächsten Seitenwechsel warten) + // Wenn der SW-Controller gewechselt hat (neuer SW aktiv): + // newBuildAvailable setzen — Reload passiert beim nächsten setActive navigator.serviceWorker.addEventListener('controllerchange', () => { - window.location.reload(); + newBuildAvailable = true; }); }); }