Auto-Reload bei neuem Build: no-cache index.html, build-time Check, SW skip-waiting

This commit is contained in:
2026-05-30 01:25:37 +02:00
parent 7a76c3b86c
commit 2c521b67b7
3 changed files with 89 additions and 10 deletions

View File

@@ -82,8 +82,30 @@ app.get('/sw.js', (_req, res) => {
res.sendFile(path.join(PUBLIC, 'sw.js'));
});
app.use(express.static(PUBLIC));
app.get('*', (_req, res) => res.sendFile(path.join(PUBLIC, 'index.html')));
app.use(express.static(PUBLIC, {
setHeaders: (res, filePath) => {
// JS/CSS Assets haben Hash im Namen → lang cachen
if (filePath.match(/\.(js|css)$/) && !filePath.includes('sw.js')) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
}
}));
// index.html nie cachen
app.get('*', (_req, res) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.sendFile(path.join(PUBLIC, 'index.html'));
});
// Build-Zeit Endpoint Frontend prüft ob es eine neue Version gibt
app.get('/api/build-time', (_req, res) => {
res.setHeader('Cache-Control', 'no-store');
try {
const ver = require('fs').readFileSync(path.join(__dirname, '../version.txt'), 'utf8').trim();
res.json({ buildTime: ver });
} catch { res.json({ buildTime: 'unknown' }); }
});
app.listen(4000, () => console.log('🚀 Dicken Dock läuft auf Port 4000'));

View File

@@ -1,9 +1,27 @@
// Passiver Service Worker nur für PWA-Installierbarkeit, kein Caching
// Verhindert schwarze Seiten nach Rebuilds
// Service Worker erkennt neue Builds und lädt automatisch neu
// BUILD_TIME wird beim Deploy vom Backend via /api/build-time geliefert
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', e => e.waitUntil(
caches.keys().then(keys => Promise.all(keys.map(k => caches.delete(k))))
const CACHE_NAME = 'dickendock-v1';
self.addEventListener('install', () => {
self.skipWaiting(); // sofort aktivieren
});
self.addEventListener('activate', e => {
// Alte Caches löschen
e.waitUntil(
caches.keys()
.then(keys => Promise.all(keys.map(k => caches.delete(k))))
.then(() => self.clients.claim())
));
// Alle Requests direkt ans Netzwerk kein Intercept
);
});
// Alle Requests direkt ans Netzwerk kein Caching
self.addEventListener('fetch', e => {
e.respondWith(fetch(e.request));
});
// Auf Update-Nachricht vom Frontend reagieren
self.addEventListener('message', e => {
if (e.data === 'SKIP_WAITING') self.skipWaiting();
});

View File

@@ -2,6 +2,45 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
// ── Service Worker registrieren + Auto-Reload bei neuem Build ─────────────────
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {
// Wenn ein neuer SW wartet: sofort übernehmen lassen
const takeOver = () => {
if (reg.waiting) {
reg.waiting.postMessage('SKIP_WAITING');
}
};
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
newWorker?.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
takeOver();
}
});
});
// Wenn SW übernimmt → Seite neu laden
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
});
// Beim Start: Build-Zeit prüfen und bei neuer Version neu laden
const STORED_KEY = 'dd_build_time';
fetch('/api/build-time', { cache: 'no-store' })
.then(r => r.json())
.then(({ buildTime }) => {
const stored = localStorage.getItem(STORED_KEY);
if (stored && stored !== buildTime) {
localStorage.setItem(STORED_KEY, buildTime);
window.location.reload();
} else {
localStorage.setItem(STORED_KEY, buildTime);
}
})
.catch(() => {}); // offline kein reload
}
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode><App /></React.StrictMode>
)