Next.js App Routerで実装する最初のページルーティング

Next.js 13で導入されたApp Routerは、従来のPages Routerに代わる新しいルーティングシステムです。本記事では、App Routerの基本概念から実装まで、すぐにプロジェクトで活用できる実践的なチュートリアルを提供します。

App Routerとは何か

App Routerはファイルシステムベースのルーティングを採用しており、appディレクトリの構造がそのままURLパスとなります。Pages Routerとの最大の違いは、Server Componentsをデフォルトで使用する点と、レイアウト機能が統合されていることです。

Pages Routerではpages/about.jsを作成すれば/aboutルートが生成されますが、App Routerではより柔軟な階層構造と共有レイアウトが可能になります。

プロジェクトのセットアップ

新規プロジェクトの作成

最初に、Next.jsプロジェクトを作成します。以下のコマンドを実行してください。

npx create-next-app@latest my-app --typescript --tailwind --app

セットアップ中に表示される質問では以下を選択してください:

  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: Yes
  • App Router: Yes
  • src/ directory: Yes

セットアップが完了したら、プロジェクトディレクトリに移動して開発サーバーを起動します。

cd my-app
npm run dev

http://localhost:3000にアクセスして、Next.jsのウェルカムページが表示されれば成功です。

基本的なルーティング構造

ファイルシステムの階層構造

App Routerのディレクトリ構造は以下のようになります。各フォルダがURLパスに対応します。

app/
├── layout.tsx          # ルートレイアウト(全ページで共有)
├── page.tsx            # / ページ
├── about/
│   └── page.tsx        # /about ページ
├── blog/
│   ├── layout.tsx      # /blog 配下のレイアウト
│   ├── page.tsx        # /blog ページ
│   └── [slug]/
│       └── page.tsx    # /blog/[slug] 動的ルート
└── dashboard/
    ├── layout.tsx
    ├── page.tsx        # /dashboard ページ
    └── settings/
        └── page.tsx    # /dashboard/settings ページ

page.tsxファイルがそれぞれのルートの実体であり、ファイルが存在しないパスにアクセスすると404エラーが返されます。

ホームページの実装

まずは基本となるホームページを実装しましょう。app/page.tsxを以下のように編集します。

export default function Home() {
  return (
    <main className="flex flex-col items-center justify-center min-h-screen">
      <h1 className="text-4xl font-bold mb-4">Next.js App Router入門</h1>
      <p className="text-gray-600 mb-8">App Routerを使ったルーティング実装</p>
      <nav className="flex gap-4">
        <a href="/about" className="px-4 py-2 bg-blue-500 text-white rounded">
          About
        </a>
        <a href="/blog" className="px-4 py-2 bg-green-500 text-white rounded">
          Blog
        </a>
      </nav>
    </main>
  );
}

ナビゲーション実装の実践例

共有レイアウトの作成

App Routerの強力な機能の一つが、ページ間で共有できるレイアウトコンポーネントです。すべてのページで共通するヘッダーやナビゲーションをapp/layout.tsxに配置します。

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "My App",
  description: "Next.js App Router チュートリアル",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body className="bg-gray-50">
        <header className="bg-white shadow">
          <nav className="max-w-6xl mx-auto px-4 py-4 flex justify-between items-center">
            <a href="/" className="text-2xl font-bold text-blue-600">
              MyBlog
            </a>
            <ul className="flex gap-6">
              <li><a href="/about" className="hover:text-blue-600">About</a></li>
              <li><a href="/blog" className="hover:text-blue-600">Blog</a></li>
              <li><a href="/contact" className="hover:text-blue-600">Contact</a></li>
            </ul>
          </nav>
        </header>
        <main className="max-w-6xl mx-auto px-4 py-8">
          {children}
        </main>
        <footer className="bg-gray-800 text-white mt-12 py-6">
          <p className="text-center">&copy; 2024 MyBlog. All rights reserved.</p>
        </footer>
      </body>
    </html>
  );
}

複数ページの追加

次に、aboutページを追加します。app/about/page.tsxを作成してください。

export default function About() {
  return (
    <div className="py-8">
      <h1 className="text-3xl font-bold mb-4">About Us</h1>
      <p className="text-gray-600 mb-4">
        このアプリケーションはNext.js App Routerの学習用チュートリアルです。
      </p>
      <section className="mt-8">
        <h2 className="text-xl font-semibold mb-2">特徴</h2>
        <ul className="list-disc list-inside space-y-2 text-gray-600">
          <li>モダンなルーティングシステム</li>
          <li>Server Componentsのデフォルト利用</li>
          <li>柔軟なレイアウト構造</li>
        </ul>
      </section>
    </div>
  );
}

動的ルートの実装

ブログ記事の個別ページ

ブログのような複数の記事を扱う場合、各記事のURLは異なります。App Routerでは、角括弧を使用して動的セグメントを定義します。

まず、app/blog/page.tsxでブログ一覧を作成します。

const posts = [
  { id: 1, title: "Next.js 入門", slug: "nextjs-intro" },
  { id: 2, title: "React Hooks マスターガイド", slug: "react-hooks" },
  { id: 3, title: "TypeScript の型システム", slug: "typescript-types" },
];

export default function Blog() {
  return (
    <div>
      <h1 className="text-3xl font-bold mb-8">Blog</h1>
      <div className="grid gap-6">
        {posts.map((post) => (
          <article key={post.id} className="border rounded-lg p-6 hover:shadow-lg transition">
            <h2 className="text-xl font-semibold mb-2">
              <a href={`/blog/${post.slug}`} className="text-blue-600 hover:underline">
                {post.title}
              </a>
            </h2>
            <p className="text-gray-600">
              投稿記事の要約がここに表示されます。
            </p>
          </article>
        ))}
      </div>
    </div>
  );
}

次に、動的ルート用のディレクトリを作成します。app/blog/[slug]/page.tsxを以下のように実装してください。

interface Props {
  params: {
    slug: string;
  };
}

const postContent: Record<string, { title: string; content: string }> = {
  "nextjs-intro": {
    title: "Next.js 入門",
    content: "Next.jsはReactの上に構築されたフルスタックフレームワークです。本記事では基本から応用まで解説します。",
  },
  "react-hooks": {
    title: "React Hooks マスターガイド",
    content: "useStateやuseEffectなどのHooksを使いこなす方法を詳しく紹介します。",
  },
  "typescript-types": {
    title: "TypeScript の型システム",
    content: "型安全なコード開発を実現するTypeScriptの型システムについて学びます。",
  },
};

export default function BlogPost({ params }: Props) {
  const post = postContent[params.slug];

  if (!post) {
    return <div className="text-center py-12"><h1>記事が見つかりません</h1></div>;
  }

  return (
    <article className="max-w-2xl">
      <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
      <div className="text-gray-600 mb-8">
        投稿日: 2024年1月15日 | 読了時間: 5分
      </div>
      <div className="prose prose-sm max-w-none">
        <p>{post.content}</p>
        <p>
          これは動的ルーティングの例です。URLの{`[slug]`}部分が、paramsオブジェクトを通じてコンポーネントに渡されます。
        </p>
      </div>
      <a href="/blog" className="mt-8 inline-block text-blue-600 hover:underline">
        ← ブログ一覧に戻る
      </a>
    </article>
  );
}

よくあるハマりポイント

params の型定義の落とし穴

動的ルートのparamsの型定義を誤ると、TypeScriptエラーが発生します。特にNext.js 14以降では、Propsインターフェースを正確に定義する必要があります。

// ❌ 間違った例
export default function BlogPost({ params }) {
  // paramsの型が any になるため、型チェックが機能しない
}

// ✅ 正しい例
interface Props {
  params: {
    slug: string;
  };
}

export default function BlogPost({ params }: Props) {
  // 型安全にアクセス可能
}

ファイルベースルーティングの順序

複数の動的ルートがある場合、ファイルの順序に注意が必要です。app/blog/[slug]/page.tsxapp/blog/new/page.tsxの両方がある場合、より具体的なルート(new)を先に定義してください。

// app/blog ディレクトリ構造
blog/
├── page.tsx           # /blog
├── new/
│   └── page.tsx       # /blog/new (より具体的)
└── [slug]/
    └── page.tsx       # /blog/:slug (より汎用的)

ナビゲーション最適化

K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →