Next.js × Suspenseを活用した非同期ルーティング体験を実現する方法
Next.jsでは、Suspense機能を利用して、ルーティング時の非同期体験をよりスムーズにすることが可能です。
Suspenseによる非同期レンダリングの基本概念
まず結論から申し上げますと、Suspenseは「読み込み中の状態を意図的に待機して、ユーザーに自然なロード体験を提供する」ための仕組みです。
React公式では、以下のように紹介されております。
従来の「Loading画面の切り替え」ではなく、コンポーネント単位で非同期状態を管理できる点が魅力です。
Next.js 13以降では、このSuspenseをルーティングにも活用できるようになりました。
Next.jsのApp RouterとSuspenseの連携
Next.js 13から採用された「App Router」では、ページ単位で自動的に非同期が管理されます。
特に、以下の特徴が重要です。
| 特徴 | 説明 | 
|---|---|
| 自動の非同期化 | page.tsx や layout.tsx はデフォルトでasync対応 | 
| Streaming対応 | 画面を部分的に描画しながらロードできる(ストリーミング) | 
| Suspense統合 | データ読み込みに応じた待機・ローディング表示ができる | 
これにより、ユーザーに「待たされる感」を与えないページ体験を作り出すことが可能になります。
基本セットアップの手順
それでは、実際にNext.jsプロジェクトをセットアップし、非同期ルーティング体験を作ってみましょう。
まず、プロジェクトを作成いたします。
bashyarn create next-app@latest my-suspense-app
cd my-suspense-app
yarn dev
このとき、**App Router(pages/ではなくapp/ディレクトリ構成)**を選択してください。
Suspenseを使ったローディングUIの実装
Next.jsでは、特別なファイル構成で簡単にローディングUIを設定できます。
例えば、app/page.tsx に対して次のようにします。
tsx// app/page.tsx
export default function HomePage() {
  return (
    <main>
      <h1>ホームページ</h1>
    </main>
  );
}
この状態で、さらに隣にloading.tsxファイルを作成します。
tsx// app/loading.tsx
export default function Loading() {
  return (
    <div>
      <p>ロード中...</p>
    </div>
  );
}
これだけで、ページのロード中に「ロード中...」という画面が自動で表示されるようになります。
特に意識せずとも、Next.jsが内部でSuspenseを使い、適切に状態管理してくれるわけです。
データフェッチとSuspenseの組み合わせ
さらに一歩進めて、APIデータを取得するパターンを見てみましょう。
tsx// app/posts/page.tsx
async function getPosts() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  return res.json();
}
export default async function PostsPage() {
  const posts = await getPosts();
  return (
    <main>
      <h1>投稿一覧</h1>
      <ul>
        {posts.map((post: any) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </main>
  );
}
そして、同様にloading.tsxを用意します。
tsx// app/posts/loading.tsx
export default function LoadingPosts() {
  return (
    <div>
      <p>投稿一覧をロード中...</p>
    </div>
  );
}
これにより、「投稿一覧ページへ遷移する際」に自然なロードUIが挟まるようになります。
ユーザー体験が劇的に向上すること、間違いありません。
カスタムSuspenseコンポーネントによる高度な制御
さらに細かく制御したい場合には、<Suspense>コンポーネントを明示的に使います。
例として、子コンポーネントを遅延ロードするパターンをご紹介します。
tsx// app/posts/page.tsx
import { Suspense } from 'react';
import PostsList from './PostsList';
export default function PostsPage() {
  return (
    <main>
      <h1>投稿一覧</h1>
      <Suspense fallback={<p>リストを読み込み中...</p>}>
        <PostsList />
      </Suspense>
    </main>
  );
}
tsx// app/posts/PostsList.tsx
async function getPosts() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  return res.json();
}
export default async function PostsList() {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
この構成では、PostsList部分のみ独自にローディング制御できます。
ページ単位ではなく、一部コンポーネントだけ読み込み中にする柔軟な設計が可能です。
Next.jsとSuspenseを組み合わせた非同期ルーティング体験の後半では、さらに一歩踏み込んだテクニックをご紹介いたします。
エラー時のフォールバック表示、ストリーミング対応、Prefetch最適化など、実践に役立つノウハウを順番に解説してまいります。
エラー発生時のフォールバック制御
非同期ロードでは、エラー発生時のユーザー体験も重要です。
Next.jsでは、エラー用にerror.tsxファイルを用意することで、エラー画面を自動表示できます。
tsx// app/posts/error.tsx
'use client'; // 必須
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      <button onClick={reset}>再試行する</button>
    </div>
  );
}
ポイントは、必ず'use client';ディレクティブを付けることです。
これにより、サーバーエラー時でも即座にフォールバックUIへ切り替えることができます。
公式ドキュメントも参考にしてください。
ストリーミングによる高速部分描画
Next.jsのApp Routerでは、ページ全体がレンダリング完了する前に、部分的に表示を開始できます。
これがストリーミング対応です。
例えば、次のように意図的にサブコンポーネントを遅延ロードできます。
tsx// app/about/page.tsx
import { Suspense } from 'react';
import AboutContent from './AboutContent';
export default function AboutPage() {
  return (
    <main>
      <h1>Aboutページ</h1>
      <Suspense fallback={<p>コンテンツを読み込み中...</p>}>
        <AboutContent />
      </Suspense>
    </main>
  );
}
tsx// app/about/AboutContent.tsx
async function fetchSlowly() {
  await new Promise((resolve) => setTimeout(resolve, 5000));
  return 'このコンテンツは5秒後にロードされます';
}
export default async function AboutContent() {
  const message = await fetchSlowly();
  return <p>{message}</p>;
}
このように、ページ自体は即座に表示 され、AboutContentだけが後からストリーミングされます。
ユーザーにとって非常にスムーズな体験となります。
Prefetchを活用した遷移速度の最適化
さらに、Next.jsでは リンクにカーソルを合わせた段階で先読み(prefetch) を行い、ページ遷移を高速化できます。
通常のリンクは次のように記述します。
tsx// app/page.tsx
import Link from 'next/link';
export default function HomePage() {
  return (
    <main>
      <h1>ホーム</h1>
      <Link href="/posts">投稿一覧へ</Link>
    </main>
  );
}
このLinkコンポーネントはデフォルトでPrefetch機能が有効です。
ユーザーがリンクに近づいただけで、バックグラウンドでデータ取得が始まるため、クリック時には瞬時に表示されます。
公式ドキュメントはこちらです。
さらにカスタムPrefetchを制御したい場合は、以下のオプションも使えます。
tsx<Link href="/posts" prefetch={false}>
  投稿一覧へ(Prefetchなし)
</Link>
通常はPrefetchありで問題ありませんが、大規模サイトでは適切にPrefetchを制御することで、帯域節約や最適化が可能となります。
useTransitionとSuspenseの併用
React 18以降では、useTransitionを併用することで、さらにスムーズな体験が可能になりました。
リンククリック時などに意図的に低優先度で画面遷移を処理できます。
tsx// app/page.tsx
'use client';
import { useTransition } from 'react';
import { useRouter } from 'next/navigation';
export default function HomePage() {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();
  const handleClick = () => {
    startTransition(() => {
      router.push('/posts');
    });
  };
  return (
    <main>
      <h1>ホームページ</h1>
      <button onClick={handleClick} disabled={isPending}>
        {isPending ? '移動中...' : '投稿一覧へ'}
      </button>
    </main>
  );
}
このアプローチにより、クリック後に画面が固まらず、なめらかに遷移できるようになります。
応用テクニックまとめ
ここまで紹介した内容を踏まえると、次のようなベストプラクティスが見えてきます。
| テクニック | 活用場面 | 
|---|---|
| loading.tsxでロード画面設定 | 各ページ初回ロード時 | 
| error.tsxでエラーUI設定 | API失敗時や例外発生時 | 
<Suspense>明示利用 | コンポーネント単位で部分ロード | 
| ストリーミング対応 | 部分的な表示・高速体験 | 
| Prefetch制御 | 大規模サイトの帯域節約・UX最適化 | 
| useTransition併用 | 遷移時の固まり防止・スムーズUX実現 | 
これらを組み合わせることで、Next.jsアプリのパフォーマンスとユーザー体験を飛躍的に向上させることが可能となります。
まとめ
Next.jsのSuspenseを活用することで、非同期ルーティング体験を極めてリッチにすることができます。
- エラー時の安心感あるフォールバック
 - 途中描画(ストリーミング)による高速体験
 - Prefetch最適化による超速ページ遷移
 - useTransitionによるなめらかアクション
 
どれも、現代のウェブアプリに欠かせない要素です。
ぜひ、この記事で学んだ内容をプロジェクトに取り入れ、驚くほど快適なユーザー体験を実現してみてください。
参考にした公式リンクも改めてまとめておきます。
未来のウェブ体験をリードするアプリケーション作りに、ぜひお役立てください。
articleNext.js の 観測可能性入門:OpenTelemetry/Sentry/Vercel Analytics 連携
articleNext.js でドキュメントポータル:MDX/全文検索/バージョン切替の設計例
articleNext.js でインフィニットスクロールを実装:Route Handlers +`use` で滑らかデータ読込
articleRedis 使い方:Next.js で Cache-Tag と再検証を実装(Edge/Node 両対応)
articleNext.js の RSC 境界設計:Client Components を最小化する責務分離戦略
articleNext.js ルーティング早見表:セグメント・グループ・オプションの一枚まとめ
articleZustand × Suspense:データ取得を直感的に扱う設計パターン
articleReact 18 × Jotai 完全対応ガイド - Suspense との連携方法
articleReact × Suspenseを組み合わせてスケーラブルな非同期UIを実現する方法
articleNext.js × Suspenseを活用した非同期ルーティング体験を実現する方法
articleReact SuspenseでUIを設計する際に避けたいアンチパターンと解決策
articleReact Suspenseでデータフェッチ!fetchでは動かない理由と正しい使い方
articleWebSocket が「200 OK で Upgrade されない」原因と対処:プロキシ・ヘッダー・TLS の落とし穴
articleWebRTC 本番運用の SLO 設計:接続成功率・初画出し時間・通話継続率の基準値
articleAstro のレンダリング戦略を一望:MPA× 部分ハイドレーションの強みを図解解説
articleWebLLM が読み込めない時の原因と解決策:CORS・MIME・パス問題を総点検
articleVitest ESM/CJS 混在で `Cannot use import statement outside a module` が出る技術対処集
articleテスト環境比較:Vitest vs Jest vs Playwright CT ― Vite プロジェクトの最適解
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来