Next.js ローディング状態管理完全ガイド:loading.tsx と Suspense で作る最高のユーザー体験
最近、あるレストランの予約サイトを使っていて気づいたことがあります。
ページを開いた瞬間、真っ白な画面が2秒間続き、突然すべてのコンテンツが「ドン!」と表示されるサイトと、
最初は枠組み(スケルトン)が表示され、徐々に画像やテキストが埋まっていくサイト。
前者を使うと「あれ? スマホの電波悪いかな?」と不安になりますが、後者だと「あ、今読み込んでるな」と安心して待てます。
体感速度は同じ2秒でも、心理的なストレスは天と地ほどの差があります。
これが「ローディング UI」の力です。
Next.js App Router は、この「待機時間」の体験を劇的に改善する仕組みを持っています。loading.tsx ファイルを置くだけで、React Suspense を使ったインスタントなローディング状態を作れるのです。
でも、ただ loading.tsx を置けばいいというわけではありません。
「どこに置くか」で、ユーザー体験は大きく変わります。ルート全体をブロックすべきか? それとも特定のコンポーネントだけを遅延させるか? スケルトンスクリーンはどうやって作るのが正解か?
この記事では、Next.js (App Router) におけるローディング状態管理のすべてを解説します。白画面(White Screen of Death)を撲滅し、ユーザーに「速い!」と思わせる魔法をかけましょう。
loading.tsx の魔法:仕組みを理解する
まずは基本から。App Router では、ファイルシステムベースのルーティングを採用していますが、ローディング状態もファイルシステムで定義します。
loading.tsx というファイルを作成すると、Next.js は自動的にその階層の page.tsx や layout.tsx を <Suspense> でラップしてくれます。
実際に何が起きているのか?
以下のファイル構造を例にします:
app/
├── dashboard/
│ ├── layout.tsx
│ ├── loading.tsx <-- これを作成
│ └── page.tsx
Next.js はビルド時に、これを以下のような React ツリーに変換します:
<Layout>
<Suspense fallback={<Loading />}>
<Page />
</Suspense>
</Layout>
つまり:
- ユーザーが
/dashboardにアクセスする。 Layoutは即座にレンダリングされる(サイドバーなどはすぐ見える)。Pageがサーバーサイドでデータ取得をしている間、代わりにLoadingコンポーネントが表示される。- データ取得が完了すると、
Loadingが消えてPageが表示される。
これが「ストリーミングレンダリング」の正体です。サーバーは HTML を少しずつブラウザに送信できるので、ユーザーはずっと白い画面を見続ける必要がなくなります。
実践:シンプルなローディング画面
まずは簡単なスピナーを表示してみましょう。
// app/dashboard/loading.tsx
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-[50vh]">
<div className="w-10 h-10 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
</div>
)
}
これだけで、ダッシュボードのデータ読み込み中にスピナーが表示されるようになります。簡単ですね。
スケルトンスクリーンで体験を向上させる
スピナーも悪くありませんが、もっと良い方法があります。「スケルトンスクリーン」です。
ページのレイアウトを模したグレーのボックスを表示することで、ユーザーに「コンテンツがどんな形で表示されるか」を予告します。これにより、体感速度が向上します(Perceived Performance)。
YouTube や Facebook がやっているアレです。
スケルトンの作り方
Tailwind CSS を使えば、animate-pulse クラスで簡単に作れます。
// app/blog/loading.tsx
export default function Loading() {
return (
<div className="max-w-4xl mx-auto p-6 space-y-8">
{/* タイトルのスケルトン */}
<div className="h-10 bg-gray-200 rounded w-3/4 animate-pulse" />
{/* メタ情報のスケルトン */}
<div className="flex gap-4">
<div className="h-4 bg-gray-200 rounded w-20 animate-pulse" />
<div className="h-4 bg-gray-200 rounded w-20 animate-pulse" />
</div>
{/* 本文のスケルトン */}
<div className="space-y-4">
<div className="h-4 bg-gray-200 rounded w-full animate-pulse" />
<div className="h-4 bg-gray-200 rounded w-full animate-pulse" />
<div className="h-4 bg-gray-200 rounded w-5/6 animate-pulse" />
<div className="h-4 bg-gray-200 rounded w-4/6 animate-pulse" />
</div>
</div>
)
}
コツは、実際の page.tsx のレイアウトとできるだけ一致させることです。そうすれば、ローディングが終わった瞬間の「ガタつき(Layout Shift)」を防げます。
Suspense で部分的なローディングを実現する
loading.tsx はページ全体に対するローディングですが、時には「ページの一部だけ」をローディング中にしたいこともあります。
例えば、記事の本文はすぐ表示できるけど、コメント欄の読み込みだけ遅い場合。コメント欄のためにページ全体をブロックするのはもったいないですよね。
そんな時は、コンポーネント内で直接 <Suspense> を使います。
// app/post/[slug]/page.tsx
import { Suspense } from 'react'
import PostContent from '@/components/PostContent'
import PostComments from '@/components/PostComments'
import CommentsSkeleton from '@/components/CommentsSkeleton'
export default function PostPage({ params }) {
return (
<main>
{/* 記事本文:ここはすぐ重要なデータなのでページ全体と一緒に待つか、高速なAPIなら即表示 */}
<PostContent slug={params.slug} />
<hr className="my-8" />
{/* コメント欄:遅いので個別にLoading表示 */}
<Suspense fallback={<CommentsSkeleton />}>
<PostComments slug={params.slug} />
</Suspense>
</main>
)
}
PostComments コンポーネント内で非同期データ取得(await fetch(...))を行っていれば、React は自動的にその完了を待ち、それまでは fallback を表示します。これが Streaming Server Components の真骨頂です。
ルート変更時のローディング:ナビゲーションの即時性
ここが少し混乱しやすいポイントです。
Next.js のリンク(<Link>)をクリックした時、何が起きるでしょうか?
-
loading.tsxがある場合:
URL は即座に切り替わり、即座にloading.tsxの内容が表示されます。これを「即時ローディング状態(Instant Loading State)」と呼びます。ユーザーにとっては「サクサク動く」感覚になります。 -
loading.tsxがない場合:
次のページのデータ取得が完了するまで、ブラウザは現在のページに留まります。URL は変わりません。読み込みが終わった瞬間にパッと次のページに切り替わります。
どちらが良いかはケースバイケースです。
- ダッシュボードのようなアプリ:即座に反応してほしいので、
loading.tsx(スケルトン)推奨。 - ドキュメントサイト:読みかけの記事から勝手に飛ばされるより、準備ができてから遷移したほうが良い場合もある。その場合は
loading.tsxを作らない、という選択肢もあります。
useTransition による「待機中」の表現
loading.tsx を作らず、現在のページに留まったまま「読み込み中…」と表示したい場合はどうすればいいでしょうか?
例えば、検索フィルターを適用した時、画面を白くせずにリストだけ更新したい場合などです。
useTransition フックを使います。
'use client'
import { useTransition } from 'react'
import { useRouter } from 'next/navigation'
export default function FilterButton() {
const [isPending, startTransition] = useTransition()
const router = useRouter()
const handleFilter = () => {
startTransition(() => {
// ナビゲーションをトランジションとしてラップする
router.push('/dashboard?filter=active')
})
}
return (
<button
onClick={handleFilter}
disabled={isPending}
className={`px-4 py-2 rounded ${
isPending ? 'bg-gray-400' : 'bg-blue-600'
} text-white`}
>
{isPending ? '更新中...' : 'フィルター適用'}
</button>
)
}
こうすると、URL遷移(データ取得)が行われている間、isPending が true になります。画面は現在のまま維持され、ボタンだけがローディング状態になります。これを「ソフトナビゲーション」と呼びます。
クライアントの実践例:React DevTools でデバッグ
開発中、ローカル環境は速すぎてローディング画面が一瞬しか見えない(あるいは全く見えない)ことがあります。これではスケルトンのデザイン調整ができません。
3つの解決策があります。
-
React DevTools を使う:
コンポーネントツリーから<Suspense>を見つけ、右側の時計アイコン(“Suspend the selected component”)をクリックすると、強制的にフォールバック状態を表示できます。 -
人工的な遅延を入れる:
データ取得関数で意図的にsleepさせます。await new Promise(resolve => setTimeout(resolve, 3000)) // 3秒待つ※ 本番コードから消すのを絶対に忘れないでください!
-
Chrome のネットワークスロットリング:
DevTools の Network タブで “Slow 3G” を選ぶ。ただし、これだとアセットの読み込みも遅くなるので少しストレスです。
まとめ:最高のローディング体験のために
ローディング状態は「待ち時間」ではなく「インターフェースの一部」です。
- 基本は
loading.tsx:ルートごとのスケルトンを作って、即時反応するアプリ体験を作る。 - 重い部分は
Suspense:ページ全体を待たせず、重いコンポーネントだけを粒度細かくストリーミングする。 - 継続的な操作は
useTransition:フィルター変更やタブ切り替えなど、コンテキストを維持したい場合は画面遷移させずに状態だけ更新する。
ユーザーは「待つこと」が嫌いなのではなく、「何が起きているか分からないこと」が嫌いなのです。優れたローディング UI は、その不安を解消し、信頼感に変えることができます。
さあ、あなたのアプリから「真っ白な画面」を追放しましょう。
Next.js でスケルトンローディング画面を作成する
loading.tsx と Tailwind CSS を使用して、モダンなスケルトンローディング UI を実装する手順
- 1
Step1: loading.tsx の作成
ローディングを表示したいルートディレクトリ(例:app/dashboard/)に loading.tsx ファイルを作成します。 - 2
Step2: スケルトンコンポーネントの作成
実際のページレイアウトと似た構造のdiv要素を作成し、Tailwind CSS の `animate-pulse`、`bg-gray-200`、`rounded` クラスを適用して、脈動するグレーのボックスを作ります。 - 3
Step3: レイアウトシフトの防止
読み込み完了後のコンテンツと同じ高さ(height)や幅(width)をスケルトン要素に指定し、表示切り替え時の画面のガタつき(CLS)を防ぎます。 - 4
Step4: 動作確認
データ取得部分に一時的に `await new Promise(r => setTimeout(r, 2000))` を追加して遅延させ、スケルトンが正しく表示されるか確認します。
FAQ
loading.tsx と React Suspense の違いは何ですか?
ページ遷移時にローディング画面を表示したくない場合は?
特定のコンポーネントだけをローディング中にするには?
loading.tsx が開発環境で一瞬しか表示されず確認できません。
4 min read · 公開日: 2026年1月5日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド

コメント
GitHubアカウントでログインしてコメントできます