fix(frontend): weiße Seite bei fehlgeschlagenem Chunk-Import beheben
Build and Push Multi-Platform Images / build-and-push (push) Successful in 18s
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:
@@ -7,6 +7,7 @@ import { saveReturnUrl } from './auth/sessionRedirect';
|
|||||||
import { ThemeProvider, useTheme } from './theme/ThemeContext';
|
import { ThemeProvider, useTheme } from './theme/ThemeContext';
|
||||||
import AuthCallback from './auth/AuthCallback';
|
import AuthCallback from './auth/AuthCallback';
|
||||||
import AppLayout from './layouts/AppLayout';
|
import AppLayout from './layouts/AppLayout';
|
||||||
|
import AppErrorBoundary from './components/AppErrorBoundary';
|
||||||
import { Spin, Result, Button } from 'antd';
|
import { Spin, Result, Button } from 'antd';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
@@ -117,6 +118,7 @@ function ThemedApp() {
|
|||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<AntdApp>
|
<AntdApp>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<AppErrorBoundary>
|
||||||
<Suspense fallback={<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}><Spin size="large" /></div>}>
|
<Suspense fallback={<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}><Spin size="large" /></div>}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
@@ -143,6 +145,7 @@ function ThemedApp() {
|
|||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</AppErrorBoundary>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</AntdApp>
|
</AntdApp>
|
||||||
</AuthProvider>
|
</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>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,22 @@ import { createRoot } from 'react-dom/client';
|
|||||||
import App from './App';
|
import App from './App';
|
||||||
import './index.css';
|
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(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
Reference in New Issue
Block a user