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