Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Suspenseバウンダリによるフォールバックレンダリング、コード分割、ストリーミングSSR。
Suspenseはコンポーネントがレンダリング前に何かを「待つ」ことを可能にするメカニズムです。コンポーネントの準備ができていない場合(lazyインポートがロードされていない、またはデータフェッチが解決されていない)、Promiseをスローします。ReactはスローされたPromiseをキャッチし、ツリーの上位にある最も近いSuspenseバウンダリを見つけ、フォールバックUIを表示し、Promiseが解決したときにサブツリーを自動的に再レンダリングします。
React.lazy(() => import('./MyComponent'))は動的インポートを特別なラッパーでラップします。Reactが初めてレンダリングしようとすると、import()がまだ解決されていないため、lazyラッパーはPromiseをスローします。
レンダーフェーズ中、ReactはスローされたPromise(スローされたエラーではありません — それらはError Boundariesに行きます)をキャッチします。Fiberツリーを上に走査して最も近い<Suspense>祖先を見つけ、サスペンドされたサブツリーの代わりにfallback propをレンダリングします。
ReactはスローされたPromiseに.then()ハンドラをアタッチします。Promiseが解決されると(モジュールがロードされるかデータが到着すると)、Reactはサスペンドされたサブツリーを最初から再レンダリングし、今度は解決された値を使用します。
並行モードでは、ReactはサスペンドされたサブツリーをDOMにコミットせずに「試みる」ことができます。サスペンドすると、Reactは(フォールバックへのフラッシュなしで)古いコンテンツを表示し続け、Promiseが解決した後に新しいコンテンツをアトミックに切り替えます。
Next.js/React 18ストリーミングでは、サーバーはSuspenseフォールバックが配置されたHTMLシェルを即座に送信します。各非同期データフェッチがサーバーで解決されると、Reactは入力されたHTMLチャンクをブラウザにストリームし、インラインでフォールバックを置き換えます。
遅延ロードコンポーネントを作成するために動的インポートをラップします。最初のレンダリングでPromiseをスローします。解決後、Reactはロードされたモジュールで再レンダリングします。
<Suspense fallback='{'<Spinner/>'}'>は子孫からスローされたPromiseをキャッチします。待機中はフォールバックを表示し、準備ができたら子を表示します。
コンポーネントが準備できていないことを通知するメカニズム。lazyコンポーネントに限らず、任意のコンポーネントがSuspenseをトリガーするためにPromiseをスローできます — これがReact Queryなどのデータフェッチライブラリの統合方法です。
サーバーがHTMLを段階的に送信します。シェルはフォールバックとともに即座にレンダリングされます。非同期処理が完了すると、Reactは入力されたコンテンツチャンクをブラウザにストリームします。
複数のSuspenseバウンダリの表示順を調整します。「一緒に」(一度にすべて表示)または「前から」/「後ろから」(順番に表示)を指定できます。
1// Code splitting with React.lazy2const HeavyChart = React.lazy(() => import('./HeavyChart'));34function App() {5 return (6 <Suspense fallback={<Spinner />}>7 <HeavyChart /> {/* loads the JS bundle on demand */}8 </Suspense>9 );10}1112// Data fetching (with a Suspense-compatible library)13// The library throws a Promise internally when data isn't ready14function UserProfile({ userId }) {15 // use() hook (React 19) or library wrapper throws if not ready16 const user = use(fetchUser(userId));17 return <h1>{user.name}</h1>;18}1920<Suspense fallback={<ProfileSkeleton />}>21 <UserProfile userId={1} />22</Suspense>
Suspenseは以前は別々だった3つの問題(コード分割、ローディング状態、サーバーストリーミング)を1つの宣言的なモデルに統合します。すべてのコンポーネントでisLoadingフラグを追跡する代わりに、バウンダリとフォールバックを宣言し、Reactがオーケストレーションを処理します。これにより、データフェッチのコードが大幅に簡素化され、ローディング状態管理のバグがなくなります。
Your SPA ships a 2MB JavaScript bundle. The landing page only needs 200KB of it — the rest is for the admin panel, chart library, and markdown editor that 80% of users never visit.
Without code splitting, the browser downloads, parses, and executes 2MB of JavaScript before showing anything. On mobile (3G network + slower CPU), this means 8+ seconds before the page is interactive — most users bounce.
Use `React.lazy` + `Suspense` at route boundaries: `const AdminPanel = React.lazy(() => import('./AdminPanel'))`. The initial bundle drops to 200KB. When a user navigates to /admin, the AdminPanel chunk loads on demand. The Suspense fallback shows a skeleton while the chunk downloads.
Takeaway: Code splitting at route boundaries is the highest-impact optimization for initial load time. Wrap lazy-loaded routes in Suspense with skeleton fallbacks. Tools like Next.js do this automatically for page components, but you should also lazy-load heavy feature components within pages.
A dashboard page loads 3 things: sidebar navigation (fast), main content (medium), and analytics charts (slow). With a single Suspense boundary, the entire page shows a spinner until the slowest component (charts) finishes loading.
One Suspense boundary at the page level means the entire page is either 'loading' or 'ready'. The sidebar (50ms) and main content (200ms) are ready quickly, but the user stares at a full-page spinner for 2 seconds waiting for the charts.
Nest multiple Suspense boundaries: one around the sidebar (shows instantly), one around the main content (shows after 200ms), one around the charts (shows after 2s). Each section renders independently as its data/code becomes available. The page progressively reveals content instead of all-or-nothing.
Takeaway: Use nested Suspense boundaries to control loading granularity. The boundary placement determines the loading UX: coarse boundaries (page-level) show everything at once, fine boundaries (component-level) enable progressive, streaming-style loading.