MENU

Next.js App Router 完全ガイド – ルーティング・レイアウト・middleware

目次

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 – 読み込み中の UI
  • error.tsx – エラー時の UI
  • not-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 での開発を試してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次