ファイルシステムベースルーティングの仕組み

App Routerの最も基本的な概念は、ファイルシステムがそのままルーティング構造になるという設計です。 Pages Routerではpages/ディレクトリが使われていましたが、App Routerではapp/ディレクトリが起点となります。 各フォルダがURLセグメントに対応し、そのフォルダ内の規約ファイルがUIの振る舞いを定義します。

app/
├── layout.tsx          # ルートレイアウト(必須)
├── page.tsx            # / のUI
├── about/
│   └── page.tsx        # /about のUI
├── blog/
│   ├── layout.tsx      # /blog 以下の共通レイアウト
│   ├── page.tsx        # /blog のUI(記事一覧)
│   └── [slug]/
│       └── page.tsx    # /blog/:slug のUI(記事詳細)
└── dashboard/
    ├── layout.tsx      # /dashboard 以下の共通レイアウト
    ├── page.tsx        # /dashboard のUI
    └── settings/
        └── page.tsx    # /dashboard/settings のUI

重要なポイントは、フォルダの存在だけではルートは作成されないということです。 フォルダ内にpage.tsxが存在して初めて、そのパスがパブリックにアクセス可能なルートになります。 これにより、コンポーネントやユーティリティファイルを同じフォルダ内に安全にコロケーション(同じ場所に配置)できます。

規約ファイルの一覧

App Routerでは、特定のファイル名が特別な意味を持ちます。これらを規約ファイル(Convention Files)と呼びます。

ファイル名役割レンダリングタイミング
page.tsxルートのメインUI。これがないとルートは公開されないリクエスト時 or ビルド時
layout.tsx子ルート間で共有されるレイアウト。状態を保持する初回のみ(再レンダリングされない)
template.tsxlayoutと似るがナビゲーション毎に再マウントされるナビゲーション毎
loading.tsxSuspenseフォールバック。ストリーミング中に表示ページロード中
error.tsxError Boundary。ランタイムエラーのキャッチエラー発生時
not-found.tsxnotFound()呼び出し時のUI404時
default.tsxParallel Routesのフォールバックスロットが未マッチ時

Dynamic Routes — 動的ルーティング

ブログ記事やユーザープロフィールのように、URLの一部が動的に変化するケースではDynamic Routesを使います。 フォルダ名を角括弧[]で囲むことで、そのセグメントをパラメータとしてキャプチャできます。

構文パターンマッチ例paramsの値
[slug]単一セグメント/blog/hello-world{ slug: "hello-world" }
[...slug]キャッチオール(1つ以上)/docs/a/b/c{ slug: ["a", "b", "c"] }
[[...slug]]オプショナルキャッチオール/docs or /docs/a/b{ slug: undefined } or { slug: ["a", "b"] }
// app/blog/[slug]/page.tsx
// Next.js 15以降、paramsはPromiseとして渡される

type Props = {
  params: Promise<{ slug: string }>
}

export default async function BlogPost({ params }: Props) {
  const { slug } = await params
  const post = await getPost(slug)

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  )
}

// 静的生成する場合はgenerateStaticParamsを定義
export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

Parallel Routes — 同一レイアウト内の複数スロット

Parallel Routesは、同じレイアウト内で複数の独立したページを同時にレンダリングする機能です。 ダッシュボードのように、1つのページ内に複数の独立したセクション(チーム情報、アナリティクス等)を表示したい場合に威力を発揮します。

@folderという命名規約を使ってスロットを定義します。この@プレフィックス付きフォルダはURLセグメントには反映されません。

app/dashboard/
├── layout.tsx           # @team と @analytics を受け取る
├── page.tsx             # /dashboard のメインUI
├── @team/
│   ├── page.tsx         # チーム情報スロット
│   └── default.tsx      # フォールバック(Next.js 16で必須)
├── @analytics/
│   ├── page.tsx         # アナリティクススロット
│   └── default.tsx      # フォールバック(Next.js 16で必須)
└── default.tsx          # childrenスロットのフォールバック
// app/dashboard/layout.tsx
// スロットはlayoutのpropsとして受け取る

export default function DashboardLayout({
  children,      // page.tsx の内容(暗黙のスロット)
  team,          // @team/page.tsx の内容
  analytics,     // @analytics/page.tsx の内容
}: {
  children: React.ReactNode
  team: React.ReactNode
  analytics: React.ReactNode
}) {
  return (
    <div className="grid grid-cols-2 gap-4">
      <div>{children}</div>
      <div>{team}</div>
      <div>{analytics}</div>
    </div>
  )
}

Intercepting Routes — ルートのインターセプト

Intercepting Routesは、現在のレイアウトを維持したまま別のルートのコンテンツを表示する機能です。 典型的なユースケースは、写真一覧からモーダルで写真詳細を表示し、URLを共有すると専用ページが開くパターン(Instagramのような挙動)です。

規約意味
(.)同じレベルのルートをインターセプト(.)/photo → 同階層のphoto
(..)1つ上のレベルをインターセプト(..)/photo → 親階層のphoto
(..)(..)2つ上のレベルをインターセプト2階層上のルート
(...)ルート(app)からインターセプト(...)/photo → app直下のphoto
// 写真ギャラリーでのIntercepting Routes + Parallel Routes
app/
├── layout.tsx
├── @modal/
│   ├── (..)photo/[id]/    # /photo/:id をインターセプト
│   │   └── page.tsx       # モーダルとして表示
│   └── default.tsx        # モーダル非表示時
├── gallery/
│   └── page.tsx           # 写真一覧
└── photo/
    └── [id]/
        └── page.tsx       # 直接アクセス時のフルページ表示

この設計により、ギャラリーページから写真をクリックするとURLは/photo/123に変わるがモーダルで表示され、/photo/123を直接URLバーに入力すると専用のフルページが表示されます。 1つのURLに対して2つの異なるUI表現を実現できるのがIntercepting Routesの強みです。

Route Groups — URLに影響しないフォルダ分類

Route Groupsは、URLパスに反映せずにルートを論理的にグループ化する機能です。 フォルダ名を丸括弧()で囲みます。

app/
├── (marketing)/          # URLには /marketing は含まれない
│   ├── layout.tsx        # マーケティングページ専用レイアウト
│   ├── about/
│   │   └── page.tsx      # /about
│   └── pricing/
│       └── page.tsx      # /pricing
├── (dashboard)/          # URLには /dashboard は含まれない
│   ├── layout.tsx        # ダッシュボード専用レイアウト(認証付き)
│   ├── overview/
│   │   └── page.tsx      # /overview
│   └── settings/
│       └── page.tsx      # /settings
└── layout.tsx            # ルートレイアウト

Route Groupsの主な用途は3つあります。

  • レイアウトの分離: マーケティングページとダッシュボードで異なるレイアウトを使い分ける
  • コードの整理: 機能やチームごとにルートをグループ化する
  • 複数ルートレイアウト: app直下に複数の(group)/layout.tsxを置くことで、ルートレベルで異なるレイアウトを実現

レイアウトシステムの設計

App Routerのレイアウトシステムは、ネストされたレイアウトの自動合成という強力な機能を提供します。 各レベルのlayout.tsxは自動的にネストされ、親から子へと包含関係を形成します。

graph TD
    A["RootLayout (app/layout.tsx)\nHTML, body, グローバルCSS"] --> B["DashboardLayout (app/dashboard/layout.tsx)\nサイドバー, ナビゲーション"]
    B --> C["SettingsLayout (app/dashboard/settings/layout.tsx)\nタブメニュー"]
    C --> D["page.tsx\n実際のコンテンツ"]
    A --> E["loading.tsx\nSuspenseフォールバック"]
    A --> F["error.tsx\nError Boundary"]
    A --> G["not-found.tsx\n404 UI"]
    style A fill:#3b82f6,stroke:#1d4ed8,color:#fff
    style B fill:#8b5cf6,stroke:#6d28d9,color:#fff
    style C fill:#10b981,stroke:#059669,color:#fff
    style D fill:#f97316,stroke:#ea580c,color:#fff
レイアウトのネスト構造: 各レベルのlayout.tsxが自動的に包含関係を形成する

レイアウトの最も重要な特性は、ナビゲーション時に再レンダリングされないことです。/dashboard/settingsから/dashboard/analyticsに遷移しても、DashboardLayoutは再マウントされず、サイドバーの状態(スクロール位置、開閉状態等)が保持されます。

layout.tsx と template.tsx の使い分け

template.tsxlayout.tsxと同じ構文ですが、ナビゲーション毎に新しいインスタンスが作成されます。 つまり、状態がリセットされ、エフェクトが再実行されます。

特性layout.tsxtemplate.tsx
ナビゲーション時再レンダリングされない(状態保持)再マウントされる(状態リセット)
ユースケースサイドバー、ナビゲーション、共通UIページ遷移アニメーション、ログ記録、フォームリセット
パフォーマンス高い(DOM再利用)低い(毎回再生成)
推奨度デフォルトで使用特殊なケースのみ

loading.tsx と error.tsx の自動ラップ

loading.tsxerror.tsxは、フレームワークが自動的にReactのSuspenseErrorBoundaryでラップします。 開発者が明示的にこれらのコンポーネントを配置する必要はありません。

// loading.tsx — Suspenseフォールバックとして自動的にラップされる
export default function Loading() {
  return (
    <div className="animate-pulse">
      <div className="h-8 bg-gray-700 rounded w-1/3 mb-4" />
      <div className="h-4 bg-gray-700 rounded w-full mb-2" />
      <div className="h-4 bg-gray-700 rounded w-2/3" />
    </div>
  )
}

// error.tsx — ErrorBoundaryとして自動的にラップされる
// 必ず 'use client' が必要(Error Boundaryはクライアントコンポーネント)
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>再試行</button>
    </div>
  )
}

proxy.ts — リクエスト処理フロー

Next.js 16では、従来のmiddleware.tsproxy.tsに置き換えられました。proxy.tsはNode.jsランタイムで動作し、従来のEdge Runtime制約から解放されています。

// proxy.ts(プロジェクトルートに配置)
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  // 認証チェック
  const token = request.cookies.get('session-token')
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  // i18nリダイレクト
  const locale = request.headers.get('accept-language')?.split(',')[0] || 'ja'
  if (request.nextUrl.pathname === '/') {
    return NextResponse.redirect(new URL('/' + locale, request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
sequenceDiagram
    participant C as Client
    participant P as proxy.ts
    participant R as Router
    participant L as Layout
    participant PG as Page

    C->>P: リクエスト送信
    P->>P: 認証・リダイレクト判定
    alt リダイレクト
        P-->>C: 302 Redirect
    else 通常処理
        P->>R: ルーティング解決
        R->>L: レイアウト階層の構築
        L->>PG: page.tsxのレンダリング
        PG-->>C: HTML + RSC Payload
    end
proxy.ts を起点としたリクエスト処理フロー

Next.js 16でのルーティング関連変更

Next.js 16では、App Routerのルーティングシステムにいくつかの重要な変更が加えられました。

変更点v15以前v16
Parallel Routesのdefault.js暗黙的にフレームワークが提供全スロットに明示的なdefault.jsが必須
ミドルウェアmiddleware.ts(Edge Runtime)proxy.ts(Node.jsランタイム)
非同期paramsオブジェクトとして直接アクセスPromise(await params必須)
ルートセグメント設定export const dynamicuse cacheディレクティブへの移行推奨
Turbopackオプトインnext devでデフォルト有効

まとめ

App Routerのルーティングシステムは、ファイルシステムの構造がそのままアプリケーションのルーティング・レイアウト構造になるという 直感的な設計を基盤としつつ、Parallel Routes、Intercepting Routes、Route Groupsといった高度なパターンで 複雑なUIニーズにも対応しています。

layout.tsxによる状態を保持するネストレイアウト、loading.tsxerror.tsxによる自動Suspense/ErrorBoundary、 そしてNext.js 16でのproxy.ts移行とdefault.js必須化は、 フレームワークが「暗黙の挙動」から「明示的な宣言」へと進化している方向性を示しています。

次章では、このルーティングシステムの中で動作するServer ComponentsとClient Componentsのレンダリングモデルを解説し、 RSC Payloadの仕組みからバンドルサイズ削減の実測データまでを深掘りします。

理解度チェック

問題 0 / 50%
Q1

App Routerでフォルダがパブリックにアクセス可能なルートになるために必須なファイルはどれですか?

キーボード: 1〜4 で選択、Enter で回答