From 2c521b67b77bebfe151de5c7c16c4c4192cad4d3 Mon Sep 17 00:00:00 2001 From: Dicken Date: Sat, 30 May 2026 01:25:37 +0200 Subject: [PATCH] Auto-Reload bei neuem Build: no-cache index.html, build-time Check, SW skip-waiting --- backend/src/index.js | 26 ++++++++++++++++++++++++-- frontend/public/sw.js | 34 ++++++++++++++++++++++++++-------- frontend/src/main.jsx | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/backend/src/index.js b/backend/src/index.js index 56c58ad..84c7335 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -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')); diff --git a/frontend/public/sw.js b/frontend/public/sw.js index e91eadf..f31782d 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -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)))) - .then(() => self.clients.claim()) -)); -// Alle Requests direkt ans Netzwerk – kein Intercept +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 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(); +}); diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 75c38d2..78e05c9 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -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( )