React SuspenseとErrorBoundaryを組み合わせたウォーターフォール通信の回避
React Frontend Performance TypeScript
結論
非独立データは Promise.all 等で並列起動し、直列的な Suspense 停止を防ぎます。
// React公式仕様:Promise.all による通信並列化と Suspense の組み合わせ
import { Suspense, use } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
// 1. 通信を並列起動(Promise.all)
async function fetchDashboardData() {
const [user, posts] = await Promise.all([
fetch('/api/user').then(res => res.json()),
fetch('/api/posts').then(res => res.json()),
]);
return { user, posts };
}
// 2. コンポーネント側で受ける
function DashboardContent({ dataPromise }) {
const { user, posts } = use(dataPromise); // React 19 use フック
return <div>{user.name} の投稿数: {posts.length}</div>;
}
React公式仕様:なぜウォーターフォール問題が起きるのか?
React公式ドキュメント(react.dev/reference/react/Suspense)で警告されている通り、親コンポーネントと子コンポーネントがそれぞれ別個に非同期データ取得を行う設計になっている場合、以下の遅延(Render-then-Fetch)が発生します:
- 親コンポーネントがマウントされ、親の通信が開始
- 親の通信が終わるまで Suspense の fallback が表示
- 親の通信完了後に初めて子コンポーネントが描画され、子の通信が開始
- 再度 Suspense が発生し、合計待機時間が「親の通信時間 + 子の通信時間」に増大
実際に起こる障害:階段状ネットワーク遅延事故
DevTools の Network タブで通信ログを確認した際、リクエストが階段状(ウォーターフォール)に伸びている場合、本来並列で取れるはずのAPI群が直列実行され、ユーザーの待ち時間が2〜3倍に跳ね上がる表示遅延障害 が発生しています。
解決手順
- 互いに依存しないデータ群(ユーザー情報・投稿一覧等)は、個別のコンポーネント内で独立して
fetchを呼ぶのをやめる - 親コンポーネントまたはルートレベルで
Promise.all([fetchA(), fetchB()])を呼び出し、同時にリクエストを発行する SuspenseとErrorBoundaryでコンポーネント全体を包み、ローディングとエラー表示を一括・型安全に管理する
// ⭕ 並列通信化と ErrorBoundary / Suspense の構成パターン
export default function DashboardPage() {
const dataPromise = fetchDashboardData(); // マウント前に並列リクエスト着手
return (
<ErrorBoundary fallback={<div>通信エラーが発生しました</div>}>
<Suspense fallback={<div>ダッシュボードを読み込み中...</div>}>
<DashboardContent dataPromise={dataPromise} />
</Suspense>
</ErrorBoundary>
);
}