feat: add React ErrorBoundary with retry and reload UI

Add class-based ErrorBoundary component that catches rendering errors
and shows a user-friendly fallback with retry/reload buttons. Wrap in
root layout to protect against full-page crashes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
머니페니 2026-03-18 22:22:29 +09:00
parent f12709ea79
commit 815f255ff5
2 changed files with 77 additions and 1 deletions

View File

@ -3,6 +3,7 @@ import { Inter, Noto_Sans_KR } from 'next/font/google';
import './globals.css';
import { ThemeProvider } from '@/components/providers/theme-provider';
import { Toaster } from '@/components/ui/sonner';
import { ErrorBoundary } from '@/components/error-boundary';
const inter = Inter({
subsets: ['latin'],
@ -34,7 +35,9 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
{children}
<ErrorBoundary>
{children}
</ErrorBoundary>
<Toaster />
</ThemeProvider>
</body>

View File

@ -0,0 +1,73 @@
'use client';
import React from 'react';
import { Button } from '@/components/ui/button';
interface ErrorBoundaryProps {
children: React.ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
}
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): ErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
handleRetry = () => {
this.setState({ hasError: false });
};
render() {
if (this.state.hasError) {
return (
<div className="flex min-h-screen items-center justify-center bg-background">
<div className="text-center space-y-4 p-8">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-destructive/10 mb-2">
<svg
className="w-8 h-8 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<h2 className="text-xl font-semibold text-foreground">
</h2>
<p className="text-muted-foreground">
.
</p>
<div className="flex gap-3 justify-center">
<Button onClick={this.handleRetry} variant="outline">
</Button>
<Button onClick={() => window.location.reload()}>
</Button>
</div>
</div>
</div>
);
}
return this.props.children;
}
}