現代のWeb開発では、瞬時の応答とスムーズな体験が求められています。このニーズに応えるため、フロントエンドフレームワークは進化を続け、パフォーマンスの最適化が重要なテーマになってきました。Next.jsは主要なReactフレームワークとして、強力なストリーミングとSuspense機能提供了。これらは、高パフォーマンスで優れたユーザー体験を提供するアプリケーションを構築するうえで欠かせない要素です。本チュートリアルでは、これらの技術の仕組みと進化の過程を深く理解し、実装コードの解説を通して、Next.jsアプリケーションでストリーミングレンダリングを活用するhambotsulong。
1. はじめに:なぜストリーミングとSuspenseが必要なのか
従来のサーバーサイドレンダリング(SSR)では、通常「すべてか何もか」という原則で動作します。サーバーは、すべてのデータ取得とページのレンダリングを完了してから、完全なHTMLドキュメントをブラウザへ送信する必要があります。いずれかのデータ取得に時間がかかると、ページ全体が準備できるまで、ユーザーは白画面またはローディングインジケーターを見るしかありません。これにより、首バイト時間(TTFB)と初回コンテンツ描画時間(FCP)が長くなり、ユーザーが感じる応答速度が低下します。
ストリーミングは、この問題を解決するために登場しました。
1.1 ストリーミングとは:「一度に注ぎ切る」から「注ぎながら飲む」へ
コップに水を注ぐ比的を、もう一度考えて、さらに深掘りしてみましょう。
- 従来のSSR(一度に注ぎ切る):蛇口がコップいっぱいに水を注ぎ終えるまで、サーバー側で全データの取得とレンダリングを完了させる必要があります。その後で、渴いたユーザーへ満杯の水を手渡します。HTML全体の送信に相当する処理です。ユーザーが待たされる時間が長くなります。
- ストリーミングSSR(注ぎながら飲む):まずコップをユーザーに渡し、そのあと蛇口を開きます。サーバーはHTMLの骨格とすぐ表示できる部分を送信し、ブラウザはページレイアウトや静的テキストなど、利用可能な部分から描画を始めます。サーバーが遅いデータを取得している間も、ユーザーはすでに飲み始められます。最終的にコップは満ち、残りデータと対応するHTML断片が順番に送信されます。長い初期待機を経ずに、内容的 Wolff が完了します。
Webアプリケーションでは、「水」をHTML、インラインJSONデータ、DOMを操作するJavaScriptコード片とみなせます。ストリーミングによって、サーバーはコンテンツを生成しながら段階的にブラウザへ送信できます。ブラウザは受信した断片を直ちに解析して描画するため、ページの一部を sooner 確認でき、インタラクティブ性と体感パフォーマンスを大幅に改善できます。
1.2 HTTPのチャンク化転送エンコーディング(Transfer-Encoding: chunked)
ストリーミングの技術的な基盤となるのが、HTTP/1.1で導入されたチャンク化転送エンコーディングです。サーバーがレスポンスを返す際、Transfer-Encoding: chunkedヘッダーを設定すると、送信前にレスポンス本体の全体サイズを知っている必要はありません。サーバーはレスポンス本体を任意の数の「チャンク」に分割できます。各チャンクにはサイズ情報とデータ本体が含まれ、順番に送信されます。最後のチャンクのサイズは0で、レスポンスの終了を示します。これにより、サーバーはコンテンツを動的に生成してすぐ送信でき、レスポンス全体をバッファする必要がなくなります。
2. コアコンセプト:React Suspense――ストリーミングレンダリングの土台
React SuspenseはReact 18で導入された中核機能であり、优雅なストリーミングレンダリングと非同期処理を実現するための鍵です。Suspense lets developers declaratively handle the loading state of components and their data.
2.1 Suspenseの設計思想
Suspenseの中心にある考え方は次のとおりです。コンポーネントは、依存するデータの準備ができるまでレンダリングを「中断」できる一方、待機時間には指定したfallback UIを表示できる。 この仕組みにより、データ取得の待機状態管理をコンポーネントロジックから分離し、Reactフレームワークに任せられます。
2.2 基本構文とfallback
この例では次のことができます。
<Suspense>コンポーネントは、コードやデータを非同期で読み込む可能性があるコンポーネント(<Comments />、<UserProfile />)を囲みます。fallbackpropにはReact要素(ローディングインジケーターやスケルトンなど)を渡し、囲まれたコンポーネントが中断している間に表示します。
2.3 Suspenseがレンダリングを「中断」する仕組み
Reactが<Suspense>で囲まれたコンポーネントをレンダリングし、そのコンポーネントまたは子コンポーネントが非同期処理を発生させた場合(React.lazyによるコンポーネントコードの読み込み、未来の実験的なuseフックによるPromiseの読み取りなど)、そのコンポーネントはPromiseを「throw」します。
가장 가까운親<Suspense>コンポーネントがこのPromiseを受け止めます。このとき次の処理が行われます。
- Reactは、そのSuspense境界内にあるすべてのコンポーネントのレンダリングを中断します。
- Reactは、そのSuspenseコンポーネントの
fallbackUIを表示します。 - ReactはPromiseを監視します。Promiseが解決(resolved)すると、中断したコンポーネントツーのレンダリングを再び試します。成功すれば、
fallbackUIを実際のコンテンツに置き換えます。
2.4 HTML内での具体的な表現
Next.jsなど、ストリーミングSSRに対応したフレームワークでは、最初のHTMLにSuspense境界が特別なプレースホルダー構造としてレンダリングされます。
この特別な<template id="boundary-id"></template>Fallback UIという構造には、次の要素が含まれます。
- 開始マーカー ``: Suspense境界の開始を示します。
$$は、再開可能な境界であることを表す可能性があります。 - 境界マーカー
<template id="boundary-id">: Suspense境界の一意な識別子としてidを持つ<template>要素です。<template>タグの中身はブラウザーの初期表示では描画されないため、プレースホルダーに適しています。 - 読み込み状態UI(
Fallback UI): 最初に表示されるfallbackコンテンツです。 - 終了マーカー ``: Suspense境界の終了を示します。
/$は境界の終了を表す可能性があります。
サーバーで実際のコンテンツの準備が整うと、実際のコンテンツを含むHTML断片(通常は、一意なIDやS:0之类的hidden属性付きの<div>)と、特定のJavaScript関数($RCなど)を呼び出す<script>タグをストリーミング送信します。この関数がfallback UIを実際のコンテンツに置き換えます。
2.5 Suspense境界とネスト
Suspenseコンポーネントはネストでき、より細かい粒度で読み込み状態を制御できます。内側のSuspense境界が、それぞれの読み込み状態を優先して処理します。
NewsFeedとChatWidgetの両方に読み込み時間が必要なら、ユーザーが最初に見るのはPageSkeletonです。その後、NavBar、Sidebar、Footerのレンダリングが完了すると、それらが表示される 동시에、NewsFeedとChatWidgetの位置にはそれぞれFeedLoadingとChatLoadingが表示されます。
3. Next.jsのストリーミングレンダリング机制の進化
Next.jsのストリーミングレンダリングは、一度vernightに完成したものではなく、段階的な進化を経てきました。初期の実装は下位のReact APIに強く依存する一方、現代の実装はより統合され、自動化的になっています。
3.1 $RCから__next_fへの抽象化
このチュートリアルで扱う$RC関数は、ストリーミング置換ロジックの基本的な簡易表現と捉えます。IDから境界とコンテンツを見つけてDOMを直接操作し、置き換えます。.streamの置換で中心となる考え方はsimplifiedで理解しやすいものです。
ところが、現代Webアプリケーションのニーズは単純なHTML置換よりはるかに複雑です。コンポーネントのprops、状態、イベントリスナーの処理、クライアントサイドのルーティングなど、より複雑なシナリオも扱う必要があります。Next.jsはこうしたケースに対応するため、より高度な仕組みを導入しました。ここで扱う__next_fキューは、その複雑な仕組みの一部です。なお、__next_fは内部の実装詳細であり、バージョンによって変わる可能性があります。本稿では現代の机制の代称として使います。
__next_fキューモデルでは、事前レンダリングしたHTMLと単純な置換関数の呼び出しだけを送るのではありません。シリアライズされたレンダリング指示とコンポーネントデータを送信します。クライアント側のNext.jsランタイムがこれらの指示を処理し、次のような処理を行う可能性があります。
- 受信したHTML断片をDOMに挿入する。
- 対応するSuspense境界を探す。
- JSON形式のデータ(props、状態)を、ハイドレーション待ちのコンポーネントへ渡す。
- ハイドレーションを実行し、コンポーネントにイベントリスナーなどを付けて操作可能にする。
この方式はより柔軟で強力であり、Reactコンポーネントの完全なライフサイクルとインタラクティブ性を支えます。静的コンテンツの置換だけではありません。
4. 実践1:基本的なストリーミング($RC関数のシミュレーション)
この例では、最も基本的なストリーミングHTML置換の概念をシミュレートし、中心的な処理の流れを理解します。
4.1 シナリオ
ブログページがあり、本文はすぐ表示できる一方、コメントセクションのデータベース読み込みには時間がかかるとします。最初にページ骨格とコメントセクションの読み込み状態を表示し、コメントデータの準備が整った時点で、ストリーミング送信して読み込み状態を置き換えたいと考えます。
4.2 サーバー側の実装(Node.js + Expressの例)
4.3 クライアント側の$RC関数详解
この簡略版の$RC(boundaryId, contentId)関数は、次の処理を実行します。
- 要素の検索:
document.getElementByIdで、境界を表す<template>(boundaryTemplate)と、実際のコンテンツを保持するhiddenの<div>(contentElement)を取得します。 - コンテナーの特定:
<template>の親ノード、すなわちSuspense境界が置かれているコンテナー(boundaryContainer)を取得します。 - Fallback UIの削除:
<template>の次の兄弟ノードから順にたどり、終了コメント/$に到達して削除するまで、すべてのノードを削除します。 - 境界マーカーの削除:
<template>要素自体と、その直前の開始コメント$$を削除します。 - 新しいコンテンツの挿入:
contentElementの子ノードすべて(実際のコメントHTML)をboundaryContainerへ移動します。 - 後始末:空になった
contentElementコンテナを削除します。
4.4 制限事項
この基本実装には、主に次のような制限があります。
- 静的HTMLのみ:HTMLコンテンツを置換するだけで、JavaScriptのイベントリスナーやコンポーネント状態、つまりハイドレーションは扱いません。コメントセクションに「いいね」や「返信」などの操作が必要なら、この方法では不十分です。
- IDの手動管理:サーバー側で生成する境界ID(
B:comments)とコンテンツID(S:comments)、およびクライアント側の$RC呼び出し、開発者が手動で整合させる必要があります。 - 脆弱なDOM操作:想定外のHTML構造に遭遇すると、直接的なDOM操作が失敗する可能性があります。
- エラー処理がない:データ取得の失敗やレンダリングエラーを処理していません。
5. 実践2:現代のストリーミング(__next_fキューのシミュレーション)
この例では、サーバーが構造化された指示とデータを送信し、クライアント側のランタイムがその指示を解釈してレンダリングとハイドレーションを実行する、より現代的な方式をシミュレートします。
5.1 シナリオ
基本情報(高速に読み込み可能)と、動的に読み込まれるユーザーアクティビティFeed(読み込みに時間がかかる)を持つユーザープロフィールページがあるとします。Feed部分には、「さらに読み込む」などの操作に対応するインタラクティブ性を持たせたいと考えます。
5.2 サーバー側の実装(Node.js + Expressの例)
5.3 クライアント側の処理ロジック(modern-streaming-client.js)
__next_fキューの初期化:HTMLのhead内で早めにself.__next_f = []を初期化し、後続のpush操作先が配列になるようにします。- コンポーネントレジストリ(
ComponentRegistry):コンポーネント名と実際のレンダリングまたはハイドレーションロジックを対応付けるオブジェクトです。ここではActivityFeed.hydrate関数が、サーバーでレンダリングされたHTMLに「さらに読み込む”按钮などのイベントリスナーを設定します。 $RC関数:HTMLの置換を実行する関数は引き続き必要です。ただしクライアントランタイムの一部となり、この版の$RCはcontentIdではなくboundaryIdとcontentHtmlを受け取り、HTML文字列を直接注入します。processNextFQueue関数:self.__next_fキューの指示を順番に処理します。- 指示に含まれるペイロードJSONを解析します。
$RCを呼び出し、payload.htmlContentをpayload.boundaryIdに対応する位置に挿入します。- 挿入されたDOM要素を探します。
payload.componentNameが存在し、ComponentRegistryに定義されている場合、対応するhydrateメソッドをDOM要素、payload.props、payload.dataとともに呼び出し、インタラクティブ性を付与します。
- 初期化と監視(
initializeStreaming):DOMContentLoadedの後に実行します。- まず、既存キュー内の指示を処理します。スクリプトの読み込み完了前に、サーバーから指示が送信されている可能性があります。
- 重要:
self.__next_f.pushメソッドを上書きします。そのあとからサーバーが<script>経由でself.__next_f.push([...])を呼び出すと、上書き後のメソッドが元のpushで指示をキューに追加し、非同期に(setTimeout(processNextFQueue, 0)で)キューの処理を開始します。これにより、スクリプト実行中に届いた新しい指示も処理され、メインスレッドのブロックも避けられます。
5.4 優位性
この現代的な方式には、明的でない利点が多数あります。
- データと表現の分離:指示には構造化されたデータ(
props、data)が含まれており、クライアントはそれに基づいてより複雑なレンダリングや状態管理を行えます。 - 完全なインタラクティブ性(ハイドレーション):
hydrate関数によって、サーバーでレンダリングされた静的HTMLを「有効化」し、完全なReactコンポーネント機能とイベント処理を持たせられます。 - より強い抽象化:開発者は下層のDOM IDや置換ロジックを直接操作するより、コンポーネントとデータを扱えます。フレームワークが複雑さの大部分を処理します。
- エラー処理と復旧:この例では詳しく示していませんが、エラー境界(Error Boundaries)や、より高度な復旧戦略を統合しやすくなります。
6. $RCと__next_fの比較分析
| 特徴 | 初期の$RC実装(シミュレーション) | 現代の__next_fキュー実装(シミュレーション) |
|---|---|---|
| 複雑さ | 単純で直接的。基本概念が理解しやすい | より複雑で、シリアライズ、クライアントランタイム、ハイドレーション involved |
| データ 전달 | 困難。通常は事前レンダリングしたHTMLだけ | 強力。構造化されたPropsや非同期データも渡せる |
| インタラクティブ性 | なし(静的HTMLの置換のみ) | 完全対応(ハイドレーションでイベントリスナーと状態を追加) |
| コンポーネントモデル | Reactのコンポーネントモデル unaware | 対応。Reactコンポーネントのライフサイクルと統合可能 |
| 抽象化のレベル | 低。DOMEのIDを直接操作 | 高。開発者はコンポーネントと指示を扱う |
| 堅牢性/エラー処理 | rudimentaryで、エラーが起きやすい | より堅牢。フレームワークがエラー処理机制を提供可能 |
| 適用シーン | 概念の理解、非常に単純な静的コンテンツの挿入 | 現代Webアプリケーション。インタラクティブ性と複雑な状態管理が必要 |
核心的な違い:$RCが重視するのはHTML断片の物理的な置換です。一方、__next_fが重視するのはレンダリング指示とデータの受け渡し・実行であり、最終的にはハイドレーションを完了します。これにより、ストリーミングで読み込まれたコンテンツが、機能_pd齐全なインタラクティブなコンポーネントになります。
7. ストリーミングレンダリングを理解するための重要点
7.1 エラー処理
ストリーミングにおけるエラー処理には、特に注意が必要です。
- サーバー側のエラー:ストリーミング中にサーバーでのデータ取得やブロックのレンダリングにエラーが起きた場合、サーバーは次的方法を選べます。
- ストリームを終了し、ページの読み込みを不完全にする。
- クライアントに特別なエラー指示を送信し、クライアントのSuspense境界にエラー状態を表示する(クライアントランタイムの対応が必要)。Reactの
ErrorBoundaryコンポーネントは、クライアント側でレンダリングエラーを捕捉できます。 - エラーが起きたブロックをスキップし、他のブロックの送信を継続する(コンテンツが欠落する可能性があります)。
- クライアント側のエラー:ハイドレーション中にエラーが起きた場合、Reactの
ErrorBoundaryがそれを捕捉して代替UIを表示し、アプリケーション全体のクラッシュを防ぎます。
7.2 ストリーミングレンダリングとSEO
検索エンジンのクローラーがストリーミング内容をインデックスする能力は向上していますが、依然として注意が必要です。
- 初期HTMLが重要:最初に送信されるHTML(ストリーミングされない部分と
fallbackUI)には、重要なSEO情報(タイトル、メタ説明、主要コンテンツの骨格)を含める必要があります。 - クローラーの待ち時間:クローラーは、実際のユーザーほど長い時間すべてのストリームチャンクの読み込み完了を待,可能会ありません。重要なコンテンツは、ストリームに早く含めるか、初期HTMLに含めてください。
templateタグ:クローラーは通常<template>タグの内容をインデックスしません。これは、fallbackの裏側にある境界IDを隠すのに適しています。- サーバーコンポーネント(RSC):RSCは、以前は非同期読み込みが必要だった部分まで含めた完全なHTMLを生成できます。コンテンツがサーバー上に完全に用意されているため、SEOには非常にLilttです。
7.3 キャッシュ戦略
ストリーミングとキャッシュは組み合わせられますが、注意が必要です。
- ページ全体のキャッシュ:ページ全体のストリーミングレスポンスをキャッシュできる場合(たとえば、全ユーザーで同じコンテンツの場合)、CDNがチャンク化されたレスポンス全体をキャッシュできます。
- 部分的なキャッシュ:より高度な戦略では、各ストリームチャンクの生成に必要なデータやレンダリング結果をキャッシュし、リクエスト時に動的にストリーミングレスポンスを組み立てる場合があります。通常、より複雑なエッジコンピューティングやサーバー側ロジックが必要です。
- クライアントキャッシュ:クライアントが取得したデータは、React Query、SWR、Service Workerでキャッシュし、再読み込みを避けられます。
7.4 Next.js App Routerとの連携(React Server Components)
Next.jsのApp Router(/appディレクトリ)では、ストリーミングとSuspenseが第一級の市民として扱われます。
- デフォルトのストリーミングレンダリング:App Routerで構築したページは、デフォルトでストリーミングレンダリングされます。
loading.jsファイル:ルートセグメント(フォルダー)にloading.jsを作成すると、そのルートセグメントのpage.jsまたはlayout.jsを囲むSuspense境界が自動的に生成されます。そのセグメントのコンポーネント(特にServer Components)がデータを取得すると、loading.jsの内容がfallbackとして表示されます。jsx // app/dashboard/loading.js export default function Loading() { // 任意のUIコンポーネントを追加できる。例えばスケルトン return <div>ダッシュボードのデータを読み込み中...</div>; }- Server Components(RSC):RSCはサーバー上で実行されるコンポーネントです。RSCが
async/awaitでデータを取得すると、Reactは自動的にそのRSCをSuspense境界(通常はloading.jsまたは手動で追加した<Suspense>で提供)で囲み、結果をクライアントへストリーミング送信します。データ取得とレンダリングがシームレスに統合され、読み込み状態を手動で管理する必要がなくなります。
App RouterとServer Componentsにより、ストリーミングレンダリングの実装が大幅に簡素化され、__next_fキューの管理のような下層の机制が抽象化されます。
8. パフォーマンス最適化の戦略
ストリーミングを使っていても、最適化の余地は残されています。
優先順位付け:
- 重要なコンテンツから:ファーストビュー(Above-the-fold)のコンテンツ、核心機能に必要なデータとコンポーネントが、ストリームに早く現れるようにします。
- 操作から:ユーザーの操作に必要な要素(ボタン、フォーム)をできるだけ早くハイドレーションします。
- Suspenseのネストを活用:重要度の異なるコンテンツごとにSuspense境界を作り、重要な部分から読み込みを完了させます。
プログレッシブエンハンスメント/ハイドレーション:
- 重要なコンポーネントを優先してハイドレーション:複雑なページでは、ビューポート内や、ユーザーが最初に操作する予定のコンポーネントを先にハイドレーションできます。
- 重要でないコンポーネントのハイドレーションを遅延:ページ下部や補助機能のコンポーネントは、ビューポートに入るか、ユーザーが操作するまでハイドレーションを遅らせられます。
コード分割:
React.lazy()と動的なimport()を使い、コンポーネントコードを必要なときに読み込みます。これによって初期JavaScriptの負荷が軽くなります。ストリーミングでは、まずHTMLの骨格を送信し、遅延読み込みコンポーネントが必要になった時点で、そのコードのダウンロードとレンダリングを開始できます。
プリロード/プリコネクト:
- 後のストリームチャンクで使用する重要なリソース(JS、CSS、フォント、APIエンドポイント)には、
<link rel="preload">または<link rel="preconnect">を使い、ブラウザーによる早期取得を促します。
CodeBlock Loading...- 後のストリームチャンクで使用する重要なリソース(JS、CSS、フォント、APIエンドポイント)には、
待ちRequirementsが連鎖するリクエストの回避:
- データ取得は可能な限り並列化します。あるストリームチャンクが依存するデータに複数のリクエストが必要な場合は、クライアントでハイドレーションしてから1つずつリクエストするのではなく、サーバー側で並列に開始します。Server Componentsには、この点で自然な利点があります。
計測と監視:
- ブラウザーの開発者ツール(Networkパネルでストリーミングレスポンスを確認し、Performanceパネルでレンダリングとハイドレーションを分析)だけでなく、Vercel Analytics、New Relic、Datadogなどのパフォーマンス監視ツールを使い、ボトルネックを特定します。FCP、LCP、TTIなどの指標を測定し、ストリーミング読み込み戦略を継続的に最適化してください。
9. React Server Components(RSC)と部分/選択的ハイドレーション
ストリーミングとSuspenseは、より高度なアーキテクチャへ向かう基礎となります。
9.1 React Server Components(RSC)详解
- 定義:RSCは新しい種類のReactコンポーネントで、サーバー上でのみ実行され、レンダリングされます。サーバー側のリソース(データベース、ファイルシステム、内部API)に直接アクセスでき、そのJavaScriptコードがクライアントへ送信されることはありません。
- 利点:
- バンドルサイズゼロ:表示だけを担当するコンポーネントや、バックエンドのリソースへアクセスする必要があるコンポーネントでは、大量のJSをクライアントへ送る必要がありません。
- データへの直接アクセス:APIエンドポイントを作成せず、データ取得ロジックを簡素化できます。
- 自動コード分割:Client Components(
"use client")と組み合わせると、RSCは自然なコード分割点になります。 - ストリーミングとの完璧な組み合わせ:RSCの
async/awaitによるデータ取得は、Suspenseとストリーミングに自然に統合されます。サーバーのデータが準備できた時点で、RSCのレンダリング結果(HTMLまたは特別な指示)をクライアントへストリーミング送信できます。
- 例:7.4節の
DashboardPageを参照してください。
9.2 部分ハイドレーション(Partial Hydration)/選択的ハイドレーション(Selective Hydration)
- 目的:従来のハイドレーション(Hydration)の問題、すなわち一部のコンポーネントが非操作型であるか、一時的に操作が不要な場合でも、アプリ全体(または大きな領域)を一度にハイドレーションしなければならず、リソースを消費する問題を解消します。
- 選択的ハイドレーション(React 18の特性):React 18とSuspenseを組み合わせると、選択的ハイドレーションを実現できます。
<Suspense>で囲まれたコンテンツは、ハイドレーション過程を中断・再開できます。- Reactはユーザーが操作している領域を優先してハイドレーションします。たとえば、ページがまだストリーミングで読み込み中のとき、表示済みの
<Suspense>境界内にあるボタンをユーザーがクリックした場合、他の境界がまだデータを読み込んでいても、Reactはその境界と中身を優先してハイドレーションします。 - ハイドレーションは非ブロッキングで、ブラウザーはハイドレーション中もユーザーの入力に応答できます。
- 部分ハイドレーション(さらに進んだ概念で、Islands Architectureと関連):
- 考え方の中心は、ページの大部分を静的HTMLにし、少数のインタラクティブな「島」(Islands)だけをハイドレーションすることです。
- Astroのようなフレームワークが代表的な例です。Next.js App Routerも、RSC(デフォルトで静的)とClient Components(
"use client")を組み合わせることで同様の効果を実現しています。"use client"と指定されたコンポーネントとその子コンポーネントだけがクライアントへ送信され、ハイドレーションされます。
9.3 技術の連携
RSC、ストリーミング、Suspense、選択的ハイドレーションが連携することで、極めて高いパフォーマンスと開発体験を実現できます。
- RSCがサーバーでデータを取得してレンダリングし、HTMLまたは指示を生成します。
- ストリーミングが、その結果を段階的にクライアントへ送信します。
- Suspenseは、データの準備ができていない間に
fallbackを表示し、データが届くとコンテンツの表示を調整します。 - 選択的ハイドレーションは、必要なDOM部分へクライアントJSを効率よく付与し、ユーザーの操作を優先しながらページの応答性を維持します。
10. 実践プロジェクトと学習リソース
- デモプロジェクトを使う
- デモプロジェクトを開く
- ブラウザーの開発者ツールを開きます(通常はF12)。
- Network(ネットワーク)パネルへ切り替え、
DocまたはHTMLフィルターを選択してページを再読み込みします。/blog-basic-streamingまたは/profile-modern-streamingリクエストを観察してください。レスポンス時間が長く、"Response(レスポンス)"タブではコンテンツがチャンク単位で表示されるのが分かります。Transfer-Encoding: chunkedヘッダーにも注目してください。 - Elements(要素)パネルへ切り替え、ストリームチャンクの受信に伴うDOMの変化、特にSuspense境界の
fallbackUIが実際のコンテンツに置き換わる過程を観察します。 - Console(コンソール)パネルで追加したログを確認し、
$RCやprocessNextFQueueの実行手順を理解します。 - 現代のストリーミング例にある「さらに読み込む」ボタンを操作し、クライアントのハイドレーション後にイベント処理が機能することを確認します。
- 推奨学習ロードマップ:
- HTTPの
Transfer-Encoding: chunkedを理解する。 - Reactの
SuspenseとReact.lazyの基本的な使い方を習得する。 - Next.js Pages Routerの
getServerSideProps(従来のSSR)とgetStaticProps(SSG)を学ぶ。 - Next.js App Routerを重点的に学ぶ:
layout.js、page.js、loading.js、error.jsの役割を理解する。- Server Components(RSC)とClient Components(
"use client")の違いと使い方を習得する。 - RSCで
async/awaitによるデータ取得を練習し、loading.jsの効果を観察する。 - 手動で
<Suspense>境界を追加し、より細かい粒度での制御を試す。
- HTTPの
- 公式ドキュメントとその他のリソース:
11. まとめ
Next.jsのストリーミングとSuspense、特にApp RouterやReact Server Componentsと組み合わせた技術は、現代Webアプリケーションのパフォーマンス最適化における最前線を示しています。ページを独立して読み込めるブロックへ分割し、Suspenseで読み込み状態を优雅に処理することで、次のような効果が得られます。
- TTFBとFCPを大幅に短縮:ユーザーが意味のある内容をより早く確認できます。
- 体感パフォーマンスの向上:全体の読み込み時間が変わらない場合でも、内容を段階的に表示するほうが、長い間白い画面を見るより_lstuく感じられます。
- ユーザー体験の改善:空白や単純な回転アイコンより、骨格画面などの読み込み状態のほうが、状況を示す上下文として有用です。
- 非同期処理の簡素化:Suspenseにより、読み込み状態の管理をコンポーネントロジックから分離できます。
- リソース利用の最適化:RSCはクライアントへ送るJS量を減らし、選択的ハイドレーションはクライアントの効率を高めます。
基本的な$RC型のHTML置換から、現代的な__next_f型の指示キュー、さらにNext.js App Routerによる高度な抽象化へと進んだ過程を理解すれば、フレームワークが下層で行っている複雑な処理を認識できます。これらの技術を習得すれば、機能だけでなく、速度、応答性、優れたユーザー体験を備えたNext.jsアプリケーションを構築できるでしょう。