Server Component が非同期処理(データフェッチ等)を含む場合、その完了までページ全体のレンダリングがブロックされる。Suspense 境界で囲むことで、該当コンポーネントの処理完了を待つ間 fallback を表示しつつ、ページの残りを先行してクライアントへストリーミングできる。
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>Dashboard</h1> {/* 即時レンダリング */}
<Suspense fallback={<Skeleton />}>
<SlowDataComponent /> {/* データ取得完了後にレンダリング */}
</Suspense>
</div>
);
}
async function SlowDataComponent() {
const data = await fetch('https://api.example.com/heavy-data');
const json = await data.json();
return <div>{json.title}</div>;
}ヘッダーやサイドバーなど即時表示可能な部分と、集計処理等で数秒かかる部分がある場合、後者だけを Suspense で囲むことで遅延箇所を局所化できる。
<Suspense fallback={<Skeleton />}>
<UserProfile /> {/* API A から取得 */}
</Suspense>
<Suspense fallback={<Skeleton />}>
<Recommendations /> {/* API B から取得(低速) */}
</Suspense>各 Suspense 境界が独立しているため、先に完了したコンポーネントから順次表示される。
loading.tsx はルートセグメント全体に対する Suspense 境界として機能する。ページ内の特定の部分にのみローディング状態を適用したい場合は、手動で <Suspense> を配置する。
<Suspense> 自体は Server Component・Client Component どちらにも配置可能。React の機能であり、コンポーネントの種別に依存しない。
| コンポーネント種別 | 非同期処理の方法 | Suspense との連携 |
|---|---|---|
Server Component("use client" なし) |
async/await で直接データ取得可能 |
await の完了を Suspense が自動的に検知 |
Client Component("use client" あり) |
async コンポーネントにできない |
React.use() でPromise をunwrap、またはSWR / TanStack Query 等のライブラリ経由 |
"use client";
import { use } from 'react';
function UserProfile({ dataPromise }: { dataPromise: Promise<User> }) {
const user = use(dataPromise); // Promise を unwrap → Suspense が捕捉
return <div>{user.name}</div>;
}参考: React - use
Next.js App Router においては Server Component + Suspense の組み合わせが主流。Server Component では async/await するだけで Suspense が機能するため、明示的な連携コードが不要。Client Component 側で Suspense を使うのは、クライアント側で動的にデータを再取得するインタラクティブな UI(リアルタイム検索結果の更新等)に限定される。
SSG ではビルド時にすべての非同期処理が完了し、完成した HTML が生成される。そのため Suspense の fallback がユーザーに表示されることはなく、ストリーミングによる段階的レンダリングの恩恵は得られない。
| レンダリング方式 | Suspense の効果 |
|---|---|
| SSR(動的レンダリング) | リクエスト時にストリーミングが発生し、Suspense 境界ごとに段階的に HTML が送信される |
| SSG(静的生成) | ビルド時にすべて解決済み。Suspense を記述しても動作上のエラーにはならないが、UX 上の効果はない |