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 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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user