fix(frontend): weiße Seite bei fehlgeschlagenem Chunk-Import beheben
Build and Push Multi-Platform Images / build-and-push (push) Successful in 18s

Alle Seiten werden per React.lazy als eigene Chunks nachgeladen. Schlug ein
Import fehl (Safari: "Importing a module script failed" — z.B. nach einem
Deployment mit neuen Chunk-Namen oder bei beschädigtem, wegen
"Cache-Control: immutable" nie revalidiertem Browser-Cache), riss der Fehler
mangels ErrorBoundary den kompletten React-Baum in eine weiße Seite.

- main.tsx: Bei 'vite:preloadError' automatisch neu laden (max. 1×/Minute,
  sessionStorage-Guard gegen Reload-Schleifen)
- AppErrorBoundary (neu): Fehlermeldung mit "Seite neu laden"-Button statt
  weißer Seite; bei Chunk-Ladefehlern mit Hinweis auf Cache-Leeren
- App.tsx: ErrorBoundary um alle Routen gelegt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 10:20:25 +02:00
parent 095cc4bb02
commit 32ba772e4e
3 changed files with 81 additions and 0 deletions
+3
View File
@@ -7,6 +7,7 @@ import { saveReturnUrl } from './auth/sessionRedirect';
import { ThemeProvider, useTheme } from './theme/ThemeContext';
import AuthCallback from './auth/AuthCallback';
import AppLayout from './layouts/AppLayout';
import AppErrorBoundary from './components/AppErrorBoundary';
import { Spin, Result, Button } from 'antd';
import type { ReactNode } from 'react';
@@ -117,6 +118,7 @@ function ThemedApp() {
<AuthProvider>
<AntdApp>
<BrowserRouter>
<AppErrorBoundary>
<Suspense fallback={<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}><Spin size="large" /></div>}>
<Routes>
<Route path="/login" element={<LoginPage />} />
@@ -143,6 +145,7 @@ function ThemedApp() {
</Route>
</Routes>
</Suspense>
</AppErrorBoundary>
</BrowserRouter>
</AntdApp>
</AuthProvider>
@@ -0,0 +1,62 @@
import { Component } from 'react';
import type { ReactNode } from 'react';
import { Button, Result } from 'antd';
interface Props {
children: ReactNode;
}
interface State {
error: Error | null;
}
// Erkennt fehlgeschlagene Lazy-Chunk-Imports:
// Safari: "Importing a module script failed."
// Chrome: "Failed to fetch dynamically imported module: …"
// Firefox: "error loading dynamically imported module"
function istChunkLadefehler(error: Error): boolean {
return /module script failed|dynamically imported module/i.test(error.message);
}
/**
* Fängt Renderfehler ab, die sonst den kompletten React-Baum aushängen und
* nur eine weiße Seite hinterlassen — insbesondere fehlgeschlagene
* React.lazy-Chunk-Imports nach einem Deployment oder bei beschädigtem
* Browser-Cache (Assets werden mit "immutable" gecacht und nie revalidiert).
*/
export default class AppErrorBoundary extends Component<Props, State> {
state: State = { error: null };
static getDerivedStateFromError(error: Error): State {
return { error };
}
componentDidCatch(error: Error) {
console.error('AppErrorBoundary:', error);
}
render() {
const { error } = this.state;
if (!error) return this.props.children;
return (
<Result
status="error"
title="Die Seite konnte nicht geladen werden"
subTitle={
istChunkLadefehler(error)
? 'Ein Teil der Anwendung konnte nicht nachgeladen werden — vermutlich wurde die ' +
'Anwendung aktualisiert oder eine zwischengespeicherte Datei ist beschädigt. ' +
'Bitte neu laden. Hilft das nicht, den Browser-Cache leeren ' +
'(Safari: Verlauf → Websitedaten löschen).'
: error.message
}
extra={
<Button type="primary" onClick={() => window.location.reload()}>
Seite neu laden
</Button>
}
/>
);
}
}
+16
View File
@@ -3,6 +3,22 @@ import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
// Nach einem Deployment existieren die alten, inhalts-gehashten Chunks nicht
// mehr; zudem bleibt ein einmal fehlerhaft zwischengespeicherter Chunk wegen
// "Cache-Control: immutable" dauerhaft defekt. Vite meldet fehlgeschlagene
// dynamische Imports als 'vite:preloadError' — dann automatisch neu laden,
// damit der Browser die aktuelle index.html samt gültiger Chunk-Namen holt.
// Höchstens ein Versuch pro Minute, sonst droht eine Reload-Schleife; danach
// zeigt der AppErrorBoundary eine sichtbare Fehlermeldung.
window.addEventListener('vite:preloadError', (event) => {
const KEY = 'vite-preload-error-reload';
const letzterVersuch = Number(sessionStorage.getItem(KEY) ?? 0);
if (Date.now() - letzterVersuch < 60_000) return;
sessionStorage.setItem(KEY, String(Date.now()));
event.preventDefault();
window.location.reload();
});
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />