本PJではNext.js公式チュートリアルを元に実施します。
今回はchapter9の内容となります!
🍽️ 真っ白画面よサラバ!Next.jsの「ストリーミング」で体感速度を劇的改善!
前回、動的レンダリングには「一番遅いデータ取得(食材)が、ページ全体(コース料理)の提供を止めてしまう」という大きな課題があることを見ました。
ユーザーは、たった一つのデータのために、ページ全体が真っ白な(あるいはローディング中の)画面で待ちぼうけを食らってしまいます。
今回は、この最悪な「待ち時間」を解決するNext.jsの魔法、「ストリーミング」について見ていきましょう!
🚀 ストリーミングとは?「準備できた料理からお出しします!」作戦
ストリーミングとは、Webページ(コース料理)を小さな「チャンク(小皿)」に分割し、サーバー側で準備ができたものから順番にブラウザ(お客さん)へ送り届ける技術です。
-
従来の方法(ブロック): 前菜・スープ・メイン・デザートが全部そろうまで、お客さんを待たせる。前菜(遅いデータ)の準備に30分かかったら、お客さんは30分間何も食べられない。
-
ストリーミング: 「ドリンク(サイドバーなど静的な部分)はすぐお持ちしますね!」「スープ(速いデータ)ができましたのでどうぞ!」「申し訳ありません、メイン(遅いデータ)は調理中ですので、もう少々お待ちください!」
この方法なら、ユーザーはページの読み込み完了を待たずに、先に表示された部分を読んだり、操作したりできます。これが体感速度を劇的に向上させる秘密です。
① 超簡単!ページ全体をストリーミングする loading.tsx
Next.js (App Router) には、このストリーミングを驚くほど簡単に実装できる特殊なファイルが用意されています。
それが loading.tsx です。
-
使い方: データを読み込みたいページ(例:
/app/dashboard/)と同じ階層にloading.tsxというファイルを作るだけ! -
何が起こるの?: Next.jsは裏側で自動的にReactの
<Suspense>を使い、/dashboardのページ本体(page.tsx)がロードされるまでの間、loading.tsxの中身を「フォールバック(つなぎ)」として表示します。 -
メリット:
- サイドバーのような共通レイアウトは即座に表示されます。
- ユーザーは、メインコンテンツの読み込み中も、サイドバーをクリックして別のページに移動できます(割り込み可能なナビゲーション)。
✨ 見た目をリッチに!ローディングスケルトン
loading.tsxに「Loading...」と文字を出すだけでは味気ないですよね。 そこで使うのがローディングスケルトンです。これは、これから表示されるコンテンツの「骨組み」だけを先に見せるUIのこと。
ユーザーは「今、何が読み込まれているか」を視覚的に理解できるため、ただ待たされるよりもストレスが大幅に軽減されます。loading.tsxには、このスケルトンコンポーネントを配置するのが一般的です。
② もっと細かく!コンポーネント単位で待たせる
loading.tsxはページ全体に適用されるためお手軽ですが、「ページの中の、この部品だけが重いんだよな...」というケースもあります。
そんな時は、React標準の<Suspense>コンポーネントを使います。
-
比喩: 「コース料理のうち、調理に時間のかかるメインディッシュ(重いコンポーネント)だけ、個別にフォールバック(つなぎの小皿)を出す」イメージです。
-
実装方法:
- 重いデータ取得(例:
fetchRevenue)を、ページからコンポーネント自身(例:<RevenueChart>)の中に移動させます。(コンポーネントがasyncになります) - ページ側(
page.tsx)で、その重いコンポーネントを<Suspense>で囲み、fallbackに専用のローディングスケルトンを指定します。
- 重いデータ取得(例:
// app/dashboard/page.tsx
import { Suspense } from 'react';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import { RevenueChartSkeleton } from '@/app/ui/skeletons';
export default async function Page() {
// ...他の速いデータはここで取得
return (
<main>
{/* ...他のUI */}
<Suspense fallback={<RevenueChartSkeleton />}>
<RevenueChart />
</Suspense>
{/* ...他のUI */}
</main>
);
}
これで、RevenueChartのデータ取得が遅くても、ページの他の部分は即座に表示され、ユーザー体験を損ないません。
🧩 上級テクニック:ストリーミングを使いこなす
1.loading.tsxの適用範囲を絞る「ルートグループ ()」
loading.tsxには「子ルートにも適用されてしまう」という特性があります。 例えば、/dashboard/loading.tsxを作ると、/dashboard/invoicesや/dashboard/customersを開いたときも、同じローディングスケルトンが表示されてしまいます。
これを防ぐのがルートグループです。
-
使い方: フォルダ名をカッコ
()で囲みます。(例:/app/dashboard/(overview)/) -
効果: カッコで囲まれたフォルダ名はURLに影響しません(URLは/
dashboardのまま)。 -
活用: /dashboard/(overview)/page.tsxと/dashboard/(overview)/loading.tsxのようにファイルを配置することで、loading.tsxを「ダッシュボードのトップページにだけ」適用させることができます。
2.チカチカを防ぐ「コンポーネントのグループ化」
もし、小さなカード型コンポーネントを5個、それぞれ別々に<Suspense>で囲んだらどうなるでしょう? データが読み込めた順に「ポコッ、ポコッ」と表示され、画面がチカチカして逆に見づらくなってしまいます(ポッピング)。
-
解決策: 関連するコンポーネント群を一つのラッパーコンポーネント(例:
<CardWrapper>)でまとめ、そのラッパーごと<Suspense>で囲みます。 -
効果: バラバラに表示されるのではなく、カード群が「まとまり」として一度に表示されるため、洗練された「スタッガード(段階的)」な読み込みを実現できます。
結論:どこで待たせるかは「おもてなし」の戦略
ストリーミングを導入する際、「どこを<Suspense>で囲むか」に唯一の正解はありません。
- ページ全体を待たせるか?
- セクションごとに段階的に見せるか?
- 一番重い部品だけを個別に待たせるか?
それは、「ユーザーにどういう体験を提供したいか」「どの情報を優先して見せたいか」を考える、UI/UXの戦略そのものなのです。
今回はここまで! ご覧いただきありがとうございます!