Next.js App Router 完全ガイド – ルーティング・レイアウト・middleware
App Router がもたらした変化
Next.js 13 で導入された App Router は、ファイルベースのルーティングを大幅に進化させました。Next.js App Router により、Server Components、ネストされたレイアウト、新しい middleware システムが実現し、モダンな Full-Stack 開発が可能になります。
「Pages Router から Next.js App Router への移行は大変」「App Router の新しい概念が多くて理解が難しい」といった声をよく聞きます。App Router はこれまでのルーティング方式から大きく変わるため、段階的な学習が必須です。
今回は、Next.js App Router の基礎から実装パターンまでを、段階的にまとめておきたいと思います。App Router を完全にマスターすることで、Next.js での開発効率が劇的に向上します。
対象となる方
- Next.js の基本的な使い方は知っている方
- Pages Router から App Router への移行を検討している方
- Server Components と Client Components の違いを学びたい方
※ このドキュメントは Next.js 14 以上で書いていきます。
App Router の基本構造
ファイルベースルーティング
App Router は app/ ディレクトリをベースに、ファイル・フォルダの構造がそのまま URL ルートになります。
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── products/
│ ├── page.tsx → /products
│ ├── [id]/
│ │ └── page.tsx → /products/1, /products/2, ...
│ └── [...slug]/
│ └── page.tsx → /products/new/2026/january, ...
└── layout.tsx → ルート レイアウト
主要なファイル名:
page.tsx– ページコンポーネント(HTTP レスポンス返す)layout.tsx– レイアウト(子ページをラップ)route.ts– API ルート(/api/ の代わり)loading.tsx– 読み込み中の UIerror.tsx– エラー時の UInot-found.tsx– 404 ページ
動的ルート:[id] と […slug]
単一の動的セグメント [id]
app/products/[id]/page.tsx
→ /products/123 (id = "123")
→ /products/abc (id = "abc")
export default function ProductPage({ params }: { params: { id: string } }) {
return <h1>Product ID: {params.id}</h1>;
}
キャッチオール […slug]
app/docs/[...slug]/page.tsx
→ /docs/getting-started (slug = ["getting-started"])
→ /docs/api/endpoints (slug = ["api", "endpoints"])
→ /docs/api/endpoints/list (slug = ["api", "endpoints", "list"])
export default function DocPage({ params }: { params: { slug: string[] } }) {
const breadcrumb = params.slug.join(' > ');
return <h1>Doc: {breadcrumb}</h1>;
}
レイアウト – ネストされた UI 構造
レイアウトの仕組み
各フォルダの layout.tsx は、そのフォルダ以下のすべてのページを統一したレイアウトでラップします。ネストされたレイアウトにより、複雑な UI 構造を簡潔に実装できます。
app/
├── layout.tsx → ルート レイアウト(全ページに適用)
├── page.tsx → ホーム
└── dashboard/
├── layout.tsx → dashboard レイアウト(/dashboard/* に適用)
├── page.tsx → /dashboard
└── settings/
├── layout.tsx → settings レイアウト
└── page.tsx → /dashboard/settings
ルート レイアウト
export const metadata = {
title: 'My App',
description: 'Generated by create next app',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>
<header>ヘッダー</header>
{children}
<footer>フッター</footer>
</body>
</html>
);
}
ネストされたレイアウト
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<aside className="sidebar">
<nav>
<a href="/dashboard">Home</a>
<a href="/dashboard/settings">Settings</a>
</nav>
</aside>
<main>{children}</main>
</div>
);
}
Server Components vs Client Components
デフォルト:Server Components
App Router のコンポーネントはデフォルトで Server Components です。サーバーサイドでレンダリングされ、データベースアクセス・シークレット・大型ライブラリなどをサーバーで処理できます。
// app/products/[id]/page.tsx - Server Component
export default async function ProductPage({ params }: { params: { id: string } }) {
// サーバーサイドでデータ取得(クライアントに JavaScript が送信されない)
const response = await fetch(`https://api.example.com/products/${params.id}`);
const product = await response.json();
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
Client Components:インタラクティブな処理
インタラクティブな処理(クリック、フォーム入力)が必要な場合は use client ディレクティブでクライアント専用に設定します。
// app/components/ProductCart.tsx
'use client';
import { useState } from 'react';
export function ProductCart() {
const [quantity, setQuantity] = useState(1);
const [isAdded, setIsAdded] = useState(false);
const handleAddToCart = () => {
// クライアントサイドのみで実行
console.log(`Added ${quantity} items to cart`);
setIsAdded(true);
};
return (
<div>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(parseInt(e.target.value))}
/>
<button onClick={handleAddToCart}>
{isAdded ? '追加済み' : 'カートに追加'}
</button>
</div>
);
}
使い分けのポイント
| Server Components | Client Components |
| データベース・API 呼び出し | useState, useEffect など hooks |
| シークレット・API キー | イベントリスナー(onClick など) |
| 大型ライブラリ(データ処理) | ブラウザ API(localStorage など) |
Middleware – リクエスト前処理
Middleware の役割
Middleware は、ページ・API ルートがリクエストを処理する前に実行される機構です。認証チェック、リダイレクト、ヘッダー設定などに使用します。
middleware.ts をプロジェクトルート(app/ と同じレベル)に配置
認証チェックの例
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token')?.value;
// /dashboard に未認証でアクセスしようとした場合、ログインページにリダイレクト
if (request.nextUrl.pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// Middleware を適用するルートを指定
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*'],
};
ヘッダー・クッキーの設定
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// カスタムヘッダーを追加
response.headers.set('X-Custom-Header', 'Hello World');
// クッキーを設定
response.cookies.set('session_id', 'abc123', {
maxAge: 60 * 60 * 24, // 1日
secure: true,
httpOnly: true,
});
return response;
}
API ルート – route.ts
API ルート の基本
App Router では route.ts ファイルで API エンドポイントを定義します。
app/
├── api/
│ ├── users/
│ │ └── route.ts → GET /api/users, POST /api/users
│ └── users/[id]/
│ └── route.ts → GET /api/users/123, PUT /api/users/123
// app/api/users/route.ts
export async function GET(request: Request) {
return Response.json({ users: [] });
}
export async function POST(request: Request) {
const data = await request.json();
// ユーザー作成処理
return Response.json({ id: 1, ...data }, { status: 201 });
}
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
return Response.json({ id: params.id, name: 'John' });
}
export async function PUT(
request: Request,
{ params }: { params: { id: string } }
) {
const data = await request.json();
return Response.json({ id: params.id, ...data });
}
Pages Router からの移行ポイント
主な変更点
| 項目 | Pages Router | App Router |
| ディレクトリ | pages/ | app/ |
| デフォルト | Client Component | Server Component |
| API ルート | pages/api/ | app/api/route.ts |
| Middleware | _middleware.ts | middleware.ts |
| レイアウト | _app.tsx, _document.tsx | layout.tsx |
| メタデータ | head タグ | metadata export |
移行時の注意点
- client hooks(useState など)は必ず
use clientをつけたコンポーネントで - データ取得は Server Components で行い、props で子に渡す
- getStaticProps, getServerSideProps は不要(async layout/page で対応)
- useRouter は
use clientコンポーネント内でのみ
実装例:ブログサイト構造
app/
├── layout.tsx → ルート レイアウト(HTML構造)
├── page.tsx → ホーム
├── blog/
│ ├── layout.tsx → ブログ固有レイアウト(サイドバー)
│ ├── page.tsx → ブログ一覧
│ ├── [slug]/
│ │ ├── page.tsx → ブログ詳細(Server Component でデータ取得)
│ │ └── layout.tsx → ブログ記事用レイアウト
│ └── [...breadcrumb]/
│ └── page.tsx → カテゴリ・タグページ
├── api/
│ ├── posts/route.ts → GET /api/posts, POST /api/posts
│ └── posts/[id]/route.ts → GET /api/posts/[id]
└── middleware.ts → 認証・ロギング
ブログ詳細ページ(Server Component)
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json());
return {
title: post.title,
description: post.excerpt,
};
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await fetch(`/api/posts/${params.slug}`, { cache: 'revalidate' }).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.date}</p>
<div>{post.content}</div>
</article>
);
}
まとめ
以上で Next.js App Router 完全ガイドを終えたいと思います。
Next.js App Router は Pages Router から大きく進化し、Server Components、ネストされたレイアウト、新しい Middleware により、モダンな Full-Stack 開発が実現できます。Next.js App Router を導入することで、最初は新しい概念に戸惑うかもしれませんが、一度慣れると、その生産性とパフォーマンスの向上に驚くでしょう。
ぜひ Next.js App Router での開発を試してみてください。
