ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
この記事の要点
- ストリーミングとは、ページ全体の完成を待たずにできたところから順に送る仕組み。遅いデータが 1 つあっても画面全体が止まらない。
- 使い方は 2 通り。ページ単位なら
loading.tsx、部分単位ならSuspenseで包む。 Suspenseは「遅い部分を切り離す道具」。取得処理をできるだけ深い位置に押し込むほど、先に出せる範囲が広がる。- クローラーには段階送信せず、完成した HTML を返す。SEO 上の不利は基本的に生じない。
- フォールバックはスピナーよりスケルトンのほうが体感が良い。実際の画面の骨格に似せる。
サーバーでデータを取ってから HTML を返す方式には、素朴に作ると「一番遅い処理に全体が引きずられる」という弱点があります。売上ランキングの集計に 2 秒かかるなら、ヘッダーもナビゲーションも 2 秒待たされる、という状態です。ストリーミングはこれを解決します。この記事では仕組みと、実際にどこで区切るかの判断を扱います。
1何が起きているのか
ストリーミングでは、サーバーはまず「すぐ確定する部分」の HTML を送り出します。遅い部分にはいったん代替表示(フォールバック)を入れておき、データが揃った時点で続きを送ってブラウザ側が差し替えます。接続は開いたままで、複数回に分けて中身が届くイメージです。
2ページ単位で区切る — loading.tsx
最も手軽なのは、ページと同じフォルダに loading.tsx を置くことです。
// app/blog/loading.tsx
export default function Loading() {
return <div>読み込み中…</div>
}
これだけで、Next.js が page.tsx 以下を自動的に Suspense 境界で包みます。遷移した瞬間にレイアウトとフォールバックが表示され、ページの描画が終わると中身が入れ替わります。
ただし適用範囲は「layout の内側、page 以下」です。同じセグメントのレイアウト自身が遅い場合、その待ち時間はこの loading.tsx では覆えません。レイアウトが cookies() を読んだり、キャッシュされていないデータを取ったりしていると、遷移そのものがブロックされます。
3部分単位で区切る — Suspense
実務でよく使うのはこちらです。遅いコンポーネントだけを Suspense で包むと、それ以外は即座に送り出せます。
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* ここは即座に届く */}
<header>
<h1>ブログ</h1>
<p>最新の投稿はこちら。</p>
</header>
<main>
{/* 中で await している部分だけが後から届く */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
ポイントは、await しているコンポーネントを Suspense の中に入れることです。ページ本体で await してから結果を Suspense の子に渡しても意味がありません。その時点で既に待ってしまっているからです。
const data = await getData() をページの先頭で実行し、その下に Suspense を置く。ページ全体が待つので境界の意味がない。取得を行う小さなコンポーネントを作り、それを
Suspense で包む。待ちの発生箇所を境界の内側へ入れる。4遅い処理を深い位置へ押し込む
「どこで区切るか」は「どこで await するか」と同義です。上のほうで待つほど、静的に確定できる範囲が狭くなります。
たとえばレイアウトの先頭で params を分解しているコードは、その値が確定するまでレイアウト全体を描画できません。値を使う場所まで Promise のまま持ち回り、そこだけを Suspense で包めば、サイドバーや children は先に送り出せます。
import { Suspense } from 'react'
// async にしない: このレイアウトは params を await しない
export default function Layout({
children,
params,
}: LayoutProps<'/shop/[slug]'>) {
return (
<div>
<Sidebar />
<Suspense fallback={<h1>読み込み中…</h1>}>
{params.then(({ slug }) => (
<SlugHeading slug={slug} />
))}
</Suspense>
{children}
</div>
)
}
この「必要になる直前まで待たない」という発想は、cookies() や headers()、searchParams にも同じように当てはまります。
5フォールバックの作り方
フォールバックは「待っていることを伝える」だけでなく「これから何が来るか」を示せると効果的です。
| フォールバック | 体感 | 向いている場面 |
|---|---|---|
| スピナー | 待たされている感覚が残りやすい | 小さな部品、ボタン内の処理 |
| スケルトン | レイアウトが動かず、完成形を予測できる | 一覧・カード・表 |
| 実データの一部 | 最も自然。タイトルや画像だけ先に出す | 詳細ページ |
スケルトンは実際の要素とサイズを合わせるのがコツです。高さが違うと差し替え時に画面が飛び、かえって印象が悪くなります。
6クローラーと配信環境
検索エンジンのクローラーは、ユーザーエージェントによって判別され、ストリーミングではなく完成した HTML を受け取ります。フォールバックだけがインデックスされる心配は基本的にありません。
一方で、この扱いには副作用があります。ブラウザ向けには事前生成済みの部分を再利用できても、クローラー向けにはリクエスト時に全体を描画し直します。そのため、事前生成のときにしか手に入らない値に依存していると、人間には見えるのにクローラーには失敗する、という状態が起こり得ます。使うデータはリクエスト時にも取得できる状態にしておいてください。
また、自前サーバーで運用する場合はインフラ側の対応も必要です。Nginx などのリバースプロキシがレスポンスをバッファリングしていると、せっかく分割して送っても最後にまとめて届いてしまいます。バッファリングを無効化する設定については 本番ビルドとセルフホスト で扱います。
7つまずきやすいところ
Suspenseで包んだのに速くならない:境界の外側でawaitしていないか確認します。- ローディングが出ない:同期処理しかしていないコンポーネントは、
Suspenseで包んでも事前生成の段階で完了します。フォールバックは表示されません。 - 境界を作りすぎる:画面のあちこちが順にちらつくと、かえって落ち着かない印象になります。区切るのは「体感で分かるほど遅い部分」だけで十分です。
- レイアウトが遅い:セグメント直下の
loading.tsxでは覆えません。レイアウト内の取得処理をSuspenseで包むか、page側へ移します。
次は、そもそも待たなくて済むようにする側の話、つまりキャッシュに進みます。
ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 Spring Frameworkのアノテーション一覧
- 7 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- PHPでのファイルアップロード方法|multipart/form-dataと$_FILES 2026-08-07 17:15:57
- PHP $_SERVERとは|DOCUMENT_ROOT・PHP_SELFなど主な要素 2026-08-07 17:15:57
- Scratchのアカウントの作り方|メールアドレスとユーザー名の登録手順 2026-08-07 17:15:57
- djangoにおけるMVCアプリケーション実装例 2026-08-07 17:15:57
- Bluetoothとは|仕組み・BLEとClassicの違い・用途 2026-08-07 17:15:57
- Spring Bootのテスト入門|@SpringBootTestとJUnitでの単体・結合テスト 2026-08-07 17:15:57
- Java とは?言語仕様・JVM・主要フレームワーク一覧 2026-08-07 17:15:56
- インストール方法(Windows版) 2026-08-07 17:15:56
- APIキー取得方法 2026-08-07 17:15:56
- 【PHPフレームワーク】Laravelの使い方 2026-08-07 17:15:56
- Scratch 作品の公開方法 2026-08-07 17:15:56
- Google OAuth 2.0 認証の実装方法 2026-08-07 17:15:56
- データベースとは?RDB / NoSQL / SQL の基礎 2026-08-07 17:15:56
- インストール(eclipseプラグイン) 2026-08-07 17:15:56
- Spring Frameworkの使い方 2026-08-07 17:15:56
コメントを削除してもよろしいでしょうか?