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によるなめらかアクション
どれも、現代のウェブアプリに欠かせない要素です。
ぜひ、この記事で学んだ内容をプロジェクトに取り入れ、驚くほど快適なユーザー体験を実現してみてください。
参考にした公式リンクも改めてまとめておきます。
未来のウェブ体験をリードするアプリケーション作りに、ぜひお役立てください。
記事Article
もっと見る- article
Docker Compose使い方:複数コンテナをまとめて管理するやり方を紹介
- article
Dockerfileの基本構文の紹介!Dockerfile内でよく使う命令まとめ!
- article
React Suspenseを使う際に避けたいアンチパターン5選と解決策について紹介
- article
React SuspenseとServer Componentsの融合:クライアントとサーバの役割分担
- article
Suspense + useTransitionで滑らかなUXを実現するやり方を紹介
- article
React Suspenseでデータフェッチ!fetchでは動かない理由と正しい書き方について紹介