fix: add ErrorBoundary to isolate client-side rendering errors
CI/CD / build-and-push (push) Successful in 1m31s Details

This commit is contained in:
Cloud User 2026-03-16 12:33:16 +09:00
parent f9605360c5
commit 7a0b1841f9
4 changed files with 93 additions and 37 deletions

View File

@ -8,6 +8,7 @@ import TimeSeriesChart from '@/components/charts/TimeSeriesChart';
import NetworkChart from '@/components/charts/NetworkChart'; import NetworkChart from '@/components/charts/NetworkChart';
import NodeTable from '@/components/report/NodeTable'; import NodeTable from '@/components/report/NodeTable';
import AlertSection from '@/components/report/AlertSection'; import AlertSection from '@/components/report/AlertSection';
import ErrorBoundary from '@/components/ErrorBoundary';
export default function Home() { export default function Home() {
const [days, setDays] = useState(7); const [days, setDays] = useState(7);
@ -51,7 +52,7 @@ export default function Home() {
if (error || !data) { if (error || !data) {
return ( return (
<div className="min-h-screen flex items-center justify-center"> <div className="min-h-screen flex items-center justify-center">
<div className="text-red-500"> : {error?.message}</div> <div className="text-red-500"> : {String(error)}</div>
</div> </div>
); );
} }
@ -59,11 +60,13 @@ export default function Home() {
return ( return (
<main className="min-h-screen bg-gray-100 p-6"> <main className="min-h-screen bg-gray-100 p-6">
<div ref={reportRef} className="max-w-7xl mx-auto space-y-6"> <div ref={reportRef} className="max-w-7xl mx-auto space-y-6">
<ReportHeader <ErrorBoundary fallback="ReportHeader 오류">
start={data.period.start} <ReportHeader
end={data.period.end} start={data.period.start}
onExportPdf={handleExportPdf} end={data.period.end}
/> onExportPdf={handleExportPdf}
/>
</ErrorBoundary>
<div className="flex gap-2"> <div className="flex gap-2">
{[1, 3, 7, 14, 30].map((d) => ( {[1, 3, 7, 14, 30].map((d) => (
@ -79,41 +82,57 @@ export default function Home() {
))} ))}
</div> </div>
<SummaryCards <ErrorBoundary fallback="SummaryCards 오류">
cpu={data.metrics.cpu} <SummaryCards
memory={data.metrics.memory} cpu={data.metrics.cpu}
disk={data.metrics.disk} memory={data.metrics.memory}
network={data.metrics.network} disk={data.metrics.disk}
/> network={data.metrics.network}
/>
</ErrorBoundary>
<TimeSeriesChart title="CPU 사용률" instances={data.metrics.cpu} unit="%" /> <ErrorBoundary fallback="CPU 차트 오류">
<TimeSeriesChart title="Memory 사용률" instances={data.metrics.memory} unit="%" /> <TimeSeriesChart title="CPU 사용률" instances={data.metrics.cpu} unit="%" />
<TimeSeriesChart title="Disk 사용률" instances={data.metrics.disk} unit="%" /> </ErrorBoundary>
<ErrorBoundary fallback="Memory 차트 오류">
<TimeSeriesChart title="Memory 사용률" instances={data.metrics.memory} unit="%" />
</ErrorBoundary>
<ErrorBoundary fallback="Disk 차트 오류">
<TimeSeriesChart title="Disk 사용률" instances={data.metrics.disk} unit="%" />
</ErrorBoundary>
{data.metrics.nas.length > 0 && ( {data.metrics.nas && data.metrics.nas.length > 0 && (
<TimeSeriesChart title="NAS 사용률" instances={data.metrics.nas} unit="%" /> <ErrorBoundary fallback="NAS 차트 오류">
<TimeSeriesChart title="NAS 사용률" instances={data.metrics.nas} unit="%" />
</ErrorBoundary>
)} )}
<NetworkChart data={data.metrics.network} /> <ErrorBoundary fallback="Network 차트 오류">
<NetworkChart data={data.metrics.network} />
</ErrorBoundary>
<div> <ErrorBoundary fallback="NodeTable 오류">
<h2 className="text-lg font-semibold text-gray-800 mb-3"> </h2> <div>
<NodeTable <h2 className="text-lg font-semibold text-gray-800 mb-3"> </h2>
nodes={data.nodes} <NodeTable
cpu={data.metrics.cpu} nodes={data.nodes}
memory={data.metrics.memory} cpu={data.metrics.cpu}
disk={data.metrics.disk} memory={data.metrics.memory}
/> disk={data.metrics.disk}
</div> />
</div>
</ErrorBoundary>
<div> <ErrorBoundary fallback="AlertSection 오류">
<h2 className="text-lg font-semibold text-gray-800 mb-3"> </h2> <div>
<AlertSection <h2 className="text-lg font-semibold text-gray-800 mb-3"> </h2>
cpu={data.metrics.cpu} <AlertSection
memory={data.metrics.memory} cpu={data.metrics.cpu}
disk={data.metrics.disk} memory={data.metrics.memory}
/> disk={data.metrics.disk}
</div> />
</div>
</ErrorBoundary>
</div> </div>
</main> </main>
); );

View File

@ -0,0 +1,37 @@
'use client';
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: string;
}
interface State {
hasError: boolean;
error: Error | null;
}
export default class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-800 text-sm font-medium">{this.props.fallback || '컴포넌트 렌더링 오류'}</p>
<p className="text-red-600 text-xs mt-1">{this.state.error?.message}</p>
<pre className="text-red-500 text-xs mt-2 overflow-auto max-h-40">{this.state.error?.stack}</pre>
</div>
);
}
return this.props.children;
}
}

View File

@ -4,7 +4,7 @@ import dynamic from 'next/dynamic';
import type { NetworkMetrics } from '@/types/metrics'; import type { NetworkMetrics } from '@/types/metrics';
import { formatTimestamp, formatBytes } from '@/lib/formatters'; import { formatTimestamp, formatBytes } from '@/lib/formatters';
const ReactECharts = dynamic(() => import('echarts-for-react'), { ssr: false }); const ReactECharts = dynamic(() => import('echarts-for-react').then(mod => mod), { ssr: false, loading: () => <div style={{height: 300}} /> });
interface NetworkChartProps { interface NetworkChartProps {
data: NetworkMetrics; data: NetworkMetrics;

View File

@ -4,7 +4,7 @@ import dynamic from 'next/dynamic';
import type { MetricInstance } from '@/types/metrics'; import type { MetricInstance } from '@/types/metrics';
import { formatTimestamp } from '@/lib/formatters'; import { formatTimestamp } from '@/lib/formatters';
const ReactECharts = dynamic(() => import('echarts-for-react'), { ssr: false }); const ReactECharts = dynamic(() => import('echarts-for-react').then(mod => mod), { ssr: false, loading: () => <div style={{height: 300}} /> });
interface TimeSeriesChartProps { interface TimeSeriesChartProps {
title: string; title: string;