Frontend: "Neue Version verfügbar"-Banner nach Deploy

Die Cache-Header waren schon optimal (index.html no-store, Assets
immutable) – aber eine bereits laufende SPA holt sich nach einem
Deploy keinen neuen Code, bis der Nutzer neu lädt. Das fängt kein
Cache-Header ab.

useVersionCheck holt die (server-seitig no-store) index.html
periodisch (5 min) + beim Zurückkehren zum Tab, extrahiert die Menge
der referenzierten Vite-Asset-Hashes als Signatur und vergleicht sie
mit dem Startstand. Ändert sie sich (= neuer Build deployt), zeigt
UpdateBanner oben im Layout einen dezenten Hinweis mit "Jetzt neu
laden".

Kein Backend-/Build-Change; im Dev-Modus No-op (Vite-Dev liefert
keine /assets/<hash>-Dateien → leere Signatur → kein Alarm).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 16:56:35 +02:00
co-authored by Claude Opus 4.7
parent fd8be1d5c4
commit 6b08762ff9
4 changed files with 124 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import { useState } from 'react';
import { RefreshCw, X } from 'lucide-react';
import { useVersionCheck } from '../hooks/useVersionCheck';
/**
* Zeigt ein dezentes Banner, sobald nach einem Deploy eine neue Frontend-
* Version verfügbar ist (siehe useVersionCheck). "Jetzt neu laden" lädt die
* Seite neu und holt damit den frischen Code; "später" blendet für die
* laufende Session aus.
*/
export default function UpdateBanner() {
const updateAvailable = useVersionCheck();
const [dismissed, setDismissed] = useState(false);
if (!updateAvailable || dismissed) return null;
return (
<div className="bg-blue-600 text-white px-6 py-2.5">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2 text-sm">
<RefreshCw className="w-4 h-4 flex-shrink-0" />
<span>Eine neue Version ist verfügbar. Bitte neu laden, um sie zu nutzen.</span>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<button
onClick={() => window.location.reload()}
className="px-3 py-1 text-sm font-medium bg-white text-blue-700 rounded hover:bg-blue-50 transition-colors"
>
Jetzt neu laden
</button>
<button
onClick={() => setDismissed(true)}
className="p-1 text-white/80 hover:text-white rounded"
title="Später"
aria-label="Hinweis ausblenden"
>
<X className="w-4 h-4" />
</button>
</div>
</div>
</div>
);
}
@@ -5,6 +5,7 @@ import { gdprApi } from '../../services/api';
import Sidebar from './Sidebar';
import ScrollToTopButton from '../ScrollToTopButton';
import BirthdayModal from '../BirthdayModal';
import UpdateBanner from '../UpdateBanner';
import { AlertTriangle, ArrowRight, Building, Shield } from 'lucide-react';
function ConsentBanner() {
@@ -80,6 +81,7 @@ export default function Layout() {
<div className="flex min-h-screen">
<Sidebar />
<div className="flex-1 flex flex-col min-w-0">
<UpdateBanner />
<ConsentBanner />
<main className="flex-1 p-8">
<Outlet />