App Routerとは?
App Routerは、Next.js 13+で導入された新しいルーティングシステムおよびアプリケーションアーキテクチャです。app/ディレクトリを使用した規約ベースのルーティング(ファイルがルートを定義)を採用し、**React Server Components(RSC)**をデフォルト機能として活用することで、レイアウト、ページ、ローディング状態、エラー処理、データ取得の方法を根本から再設計しています。
Pages Routerを「ページ中心(pages/)」と考えるなら、App Routerは「ルートセグメントとレイアウトツリー中心(app/)」と言えます。各ルートセグメントは独自のレイアウト、ローディング状態、エラーバウンダリを持つことができ、それらが組み合わさって完全なUIツリーを形成します。
なぜ重要なのか?
- 強化されたレイアウトとナビゲーション体験:ネストされたレイアウト、永続化されたレイアウト、部分的な更新をサポートし、冗長なレンダリングとリクエストを削減します。
- デフォルトでServer-first(JSの削減):ページはデフォルトでServer Componentとなり、より多くのレンダリングとデータ取得をサーバー側で行えるため、クライアント側のバンドルサイズが削減されます。
- 改善されたローディングとエラーバウンダリ:各ルートセグメントで独立して
loading.tsx/error.tsxを定義でき、ユーザー体験と保守性が向上します。 - より自然なフルスタック機能:Route Handlers(
app/api/**/route.ts)とServer Actionsにより、モノレポでのフルスタック開発がよりシームレスになります。 - 将来の機能の基盤:並列ルート、インターセプティングルート、Streaming/Suspenseなどの機能が、App Routerでより完全にサポートされます。
コアコンセプトと動作原理
ルートセグメントと規約ファイル
App Routerでは、URLは通常app/以下のディレクトリ構造によって決定されます。
page.tsx:このセグメントのページエントリーポイントlayout.tsx:このセグメントとその子セグメントで共有されるレイアウト(ネスト可能)loading.tsx:このセグメントのローディングUI(Suspenseベース)error.tsx:このセグメントのエラーバウンダリ(Client Componentである必要があります)not-found.tsx:このセグメントの404ページ
最小限の例:
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>{children}</body>
</html>
);
}
// app/page.tsx
export default function HomePage() {
return <main>ホームページ</main>;
}
Server Components vs Client Components
- デフォルトはServer Component:
"use client"を記述していないコンポーネントはサーバー上でレンダリングされ、サーバーリソース(データベース、プライベートAPI、環境変数など)に直接アクセスできます。 - インタラクティブな機能にはClient Component:ボタンのクリック、即座のフォームバリデーション、
useState/useEffectの使用などに使います。
// app/dashboard/page.tsx (Server Component、デフォルト)
import { getServerSession } from "@/lib/auth"; // 例:サーバー側でのセッションアクセス
export default async function DashboardPage() {
const session = await getServerSession();
return <div>ようこそ、{session.user.name}さん</div>;
}
// app/dashboard/ClientButton.tsx (Client Component)
"use client";
import { useState } from "react";
export function ClientButton() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>クリック数:{count}</button>;
}
ネストされたレイアウトと共有UI
App Routerのレイアウトはツリー構造で構成されます。特定のセグメントに独立したレイアウトを提供しながら、親のレイアウトを継承できます。
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="dashboard-shell">
<aside>サイドバー</aside>
<section>{children}</section>
</div>
);
}
データ取得とキャッシング(直感的な説明)
App Routerでは、一般的に以下が推奨されます:
- Server Componentで直接データを取得する(よりシンプルなアプローチ)
- 外部への
fetchリクエストはデフォルトでキャッシュと重複排除が行われます(必要に応じてcache/revalidateを設定可能) - 「送信後にUIを更新」する場合、Server Actionsを使用するか、
revalidatePath/revalidateTagと組み合わせるのが一般的です
(より詳しくは、nextjs-server-actionsやnextjs-server-componentsの関連エントリを参照してください。)
実際の使用例
シナリオ1:ローディング状態とエラーバウンダリを持つ詳細ページの構築(動的ルート)
// app/posts/[slug]/page.tsx
import { notFound } from "next/navigation";
async function getPost(slug: string) {
// 例:API/データベースから取得
const res = await fetch(`https://example.com/api/posts/${slug}`, { cache: "no-store" });
if (res.status === 404) return null;
if (!res.ok) throw new Error("読み込みに失敗しました");
return res.json();
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
// app/posts/[slug]/loading.tsx
export default function Loading() {
return <div>記事を読み込み中...</div>;
}
// app/posts/[slug]/error.tsx
"use client";
export default function Error({ reset }: { reset: () => void }) {
return (
<div>
<p>記事の読み込み中にエラーが発生しました。</p>
<button onClick={() => reset()}>再試行</button>
</div>
);
}
解説:page.tsxはサーバー上で実行されデータを取得します。リクエストが保留中の間はloading.tsxが表示され、エラーはerror.tsxでキャッチされます。リソースが見つからない場合はnotFound()を使用してセグメントの404ロジックに入ります。
シナリオ2:Layoutを使用して「アカウントセクション」に統一されたシェルと認証チェックを提供
// app/account/layout.tsx
import { redirect } from "next/navigation";
import { getServerSession } from "@/lib/auth"; // 例
export default async function AccountLayout({ children }: { children: React.ReactNode }) {
const session = await getServerSession();
if (!session) redirect("/login");
return (
<div className="account">
<header>アカウントセンター</header>
<main>{children}</main>
</div>
);
}
解説:「認証 + 統一されたシェル」をlayout.tsxに配置することで、app/account/**以下のすべてのページが自動的に保護され、各ページで繰り返し認証チェックを書く必要がなくなります。
ベストプラクティス
💡 ヒント1:デフォルトでServer Componentを使用し、インタラクティブな機能が必要な場合のみClient Componentを使用する。これにより、クライアント側のJSが大幅に削減され、データアクセスがより直感的になります。
- 共有UIをレイアウト層に配置する:ナビゲーション、サイドバー、認証チェック、グローバルプロバイダーなどは、主に
layout.tsxに記述します。 - 重要なルートセグメントにバウンダリファイルを追加する:コアページに
loading.tsx/error.tsx/not-found.tsxを追加すると、ユーザー体験が大幅に向上します。 - 動的ルートとデータ取得を予測可能にする:どのページがリアルタイムデータ(
no-store)を必要とし、どのページが増分更新(revalidate)を使用できるかを明確にします。 - ルート構造でビジネスロジックを表現する:例えば、
app/(marketing)やapp/(app)のようなルートグループ(括弧付きディレクトリ)を使用すると、URLをクリーンに保ちながら構造をより明確にできます。
⚠️ よくある落とし穴:Server Componentでブラウザ APIやReact hooks(useState/useEffect)を直接使用すること。インタラクティブな機能が必要なコンポーネントには
"use client"を追加し、明確な境界を維持してください。
よくある質問
App RouterとPages Routerのどちらかを選ばなければなりませんか?
回答:いいえ、共存できます。段階的に移行でき、新機能をapp/に配置しながら、古いページは移行が完了するまでpages/に残しておくことができます。
なぜerror.tsxはClient Componentでなければならないのですか?
回答:エラーバウンダリはクライアント側のエラー回復メカニズム(reset()など)に依存しているため、Next.jsではerror.tsxに"use client"を付ける必要があります。
layout.tsxはナビゲーションのたびに再レンダリングされますか?
回答:通常はされません。レイアウトは永続化できます(特に同じレイアウトツリー内でのナビゲーション時)。これは、App Routerが体験とパフォーマンスを向上させる重要な方法の1つです。
Route Handlers(app/api/**/route.ts)はいつ必要ですか?
回答:HTTP API(サードパーティのコールバック、Webhook、モバイルアプリ、外部システム向け)を提供する必要がある場合、Route Handlersが理想的です。フォーム送信のみの場合は、Server Actionsも検討できます。
他のコンセプトとの比較
| 機能 | App Router | Pages Router |
|---|---|---|
| ルーティングディレクトリ | app/ | pages/ |
| デフォルトのレンダリングモード | Server Components優先 | クライアント/サーバーハイブリッド(ページ中心) |
| レイアウト機能 | ネストされたレイアウト、より強力な永続化 | 手動でのラッピングと構成が必要 |
| Loading/Errorバウンダリ | ルートセグメントレベルでネイティブサポート | カスタム実装が必要 |
| 新機能のサポート | 並列/インターセプティングルート、Server Actionsなど | 限定的、または追加のソリューションが必要 |
さらに学ぶ
関連用語
nextjs-server-componentsnextjs-server-actionsnextjs-middlewarenextjs-dynamic-routesnextjs-parallel-routesnextjs-metadata-api