概要
React.lazy でコンポーネントを動的インポートし、Suspense でローディング UI を表示、ErrorBoundary で読み込み失敗時のフォールバックを実装する。コード分割によるバンドルサイズ削減と、その際のエラーハンドリングをセットで示す。
インストール
# 追加インストールは不要
実装
ErrorBoundary クラスコンポーネント
// components/ErrorBoundary.tsx
import { Component, type ReactNode } from "react";
type Props = {
fallback: ReactNode;
children: ReactNode;
};
type State = { hasError: boolean; error: Error | null };
export 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 this.props.fallback;
}
return this.props.children;
}
}
遅延読み込みするコンポーネント
// components/HeavyChart.tsx
export function HeavyChart() {
return (
<div className="rounded border p-8 text-center text-sm text-gray-600">
重いチャートコンポーネント(動的インポートされる)
</div>
);
}
React.lazy + Suspense + ErrorBoundary の組み合わせ
// app/page.tsx
"use client";
import { lazy, Suspense, useState } from "react";
import { ErrorBoundary } from "@/components/ErrorBoundary";
// React.lazy でコンポーネントを遅延読み込み
const HeavyChart = lazy(() => import("@/components/HeavyChart").then((m) => ({ default: m.HeavyChart })));
function LoadingFallback() {
return (
<div className="flex h-32 items-center justify-center rounded border border-dashed">
<p className="animate-pulse text-sm text-gray-400">読み込み中...</p>
</div>
);
}
function ErrorFallback() {
return (
<div className="rounded border border-red-200 bg-red-50 p-6 text-center">
<p className="text-sm text-red-600">コンポーネントの読み込みに失敗しました</p>
<button
onClick={() => window.location.reload()}
className="mt-3 rounded bg-red-600 px-4 py-1.5 text-xs text-white hover:bg-red-700"
>
再読み込み
</button>
</div>
);
}
export default function Page() {
const [show, setShow] = useState(false);
return (
<main className="mx-auto max-w-xl p-8">
<h1 className="mb-6 text-2xl font-bold">コード分割デモ</h1>
<button
onClick={() => setShow(true)}
className="mb-6 rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700"
>
チャートを表示
</button>
{show && (
// ErrorBoundary で読み込み失敗を補足
// Suspense でローディング UI を表示
<ErrorBoundary fallback={<ErrorFallback />}>
<Suspense fallback={<LoadingFallback />}>
<HeavyChart />
</Suspense>
</ErrorBoundary>
)}
</main>
);
}
複数コンポーネントを段階的に読み込む例
"use client";
import { lazy, Suspense } from "react";
const SectionA = lazy(() => import("@/components/SectionA").then((m) => ({ default: m.SectionA })));
const SectionB = lazy(() => import("@/components/SectionB").then((m) => ({ default: m.SectionB })));
export function PageContent() {
return (
<>
{/* 各セクションが独立してローディングされる */}
<Suspense fallback={<p className="text-sm text-gray-400">セクション A 読み込み中...</p>}>
<SectionA />
</Suspense>
<Suspense fallback={<p className="text-sm text-gray-400">セクション B 読み込み中...</p>}>
<SectionB />
</Suspense>
</>
);
}
ポイント
React.lazyは() => import(...)形式の動的インポートを受け取る。名前付きエクスポートの場合は.then((m) => ({ default: m.ComponentName }))でデフォルトエクスポートに変換するSuspenseはlazyコンポーネントのロード中にfallbackを表示する。Suspenseがないと React がエラーを投げるErrorBoundaryはSuspenseの外側に置く。ネットワークエラー等でロードに失敗したときfallbackに切り替わるnextjs-dynamic-importとの違い:next/dynamicは Next.js 固有の SSR 対応 dynamic import。React.lazyは React 標準で Client Component 専用。CSR のみでよければReact.lazyで十分react-error-boundary-componentとの違い:react-error-boundary-componentは一般的なレンダリングエラーを捕捉する。このパターンはReact.lazyのロード失敗に特化した組み合わせ