T-CREATOR

Next.js ルーティング早見表:セグメント・グループ・オプションの一枚まとめ

Next.js ルーティング早見表:セグメント・グループ・オプションの一枚まとめ

Next.js の App Router におけるルーティングは、ファイルシステムベースで直感的な設計になっています。しかし、動的セグメント、ルートグループ、パラレルルート、インターセプトルートなど多彩な機能があり、それぞれの記法や用途を整理しておくと開発がスムーズです。

本記事では、Next.js のルーティングで使える主要な構文と概念を 早見表 形式でまとめ、それぞれの背景・課題・解決策を図解を交えて解説します。

早見表

セグメント早見表

#記法URL 例説明
1[segment]app​/​blog​/​[slug]​/​page.js​/​blog​/​hello-world動的セグメント(単一パラメータ)
2[...segment]app​/​docs​/​[...slug]​/​page.js​/​docs​/​a​/​b​/​cキャッチオールセグメント(複数パラメータ)
3[[...segment]]app​/​shop​/​[[...slug]]​/​page.js​/​shop または ​/​shop​/​a​/​bオプショナルキャッチオール

グループ・特殊機能早見表

#記法説明
4(group)app​/​(marketing)​/​page.jsルートグループ(URL に含まれない論理的なフォルダ分け)
5@folderapp​/​@modal​/​page.jsパラレルルート(同一レイアウト内で複数ルートを並行表示)
6(.)folderapp​/​@modal​/​(.)photo​/​[id]​/​page.jsインターセプトルート(同階層)
7(..)folderapp​/​@modal​/​(..)photo​/​[id]​/​page.jsインターセプトルート(1 階層上)
8(..)(..)folderapp​/​@modal​/​(..)(..)photo​/​[id]​/​page.jsインターセプトルート(2 階層上)
9(...)folderapp​/​@modal​/​(...)photo​/​[id]​/​page.jsインターセプトルート(ルートから)

ファイル規約早見表

#ファイル名役割
10page.jsルートの UI を定義(公開ページ)
11layout.js複数ページで共有する UI レイアウト
12loading.jsSuspense ベースのローディング UI
13error.jsエラーバウンダリ UI
14not-found.js404 Not Found 時の UI
15route.jsAPI エンドポイント(GET, POST など)

背景

Next.js は App Router(Next.js 13 以降)を導入し、ファイルシステムベースのルーティングをさらに強化しました。従来の Pages Router では pages​/​ ディレクトリにファイルを配置するだけでルーティングが完結しましたが、App Router では以下の点が進化しています。

  • レイアウトの柔軟性: layout.js によるネストされた共有 UI
  • ストリーミング対応: React Server Components と Suspense の統合
  • 並行ルート: 同一レイアウト内で複数のページを並行表示
  • インターセプト: モーダルやドロワーなどの UI パターンをルーティングで実現

これにより、複雑な UI 構造を 宣言的かつファイル構造だけで 表現できるようになりました。

次の図は、App Router におけるファイル構造と URL の対応関係を示します。

mermaidflowchart TD
  root["app/"]
  root --> page1["page.js<br/>(ルートページ: /)"]
  root --> blog["blog/"]
  blog --> blogPage["page.js<br/>(/blog)"]
  blog --> slug["[slug]/"]
  slug --> slugPage["page.js<br/>(/blog/:slug)"]
  root --> docs["docs/"]
  docs --> catchAll["[...slug]/"]
  catchAll --> catchPage["page.js<br/>(/docs/a/b/c)"]

上図のように、ディレクトリ構造が URL パスに直接マッピングされます。動的セグメントやキャッチオールセグメントを使うことで、柔軟なルーティングを実現できます。

課題

Next.js のルーティングは強力ですが、多様な記法が存在するため、以下のような課題が生じます。

  • 記法の混乱: [slug], [...slug], [[...slug]] の違いがわかりにくい
  • グループの用途不明: (group) がどのような場面で役立つのか理解しにくい
  • インターセプトの複雑さ: (.), (..), (...) の階層指定が直感的でない
  • パラレルルートの使い所: @folder を使った並行表示の実例が少ない

これらの記法を整理せずに開発を進めると、ファイル構造が肥大化し、メンテナンス性が低下します。

次の図は、ルーティング記法の選択に悩むシーンを示します。

mermaidflowchart LR
  dev["開発者"] -->|"どの記法を使う?"| choice{"記法選択"}
  choice -->|"固定パス"| static["通常フォルダ"]
  choice -->|"1つの動的パラメータ"| dynamic["[slug]"]
  choice -->|"複数パラメータ"| catchAll["[...slug]"]
  choice -->|"論理的な分類"| group["(group)"]
  choice -->|"並行表示"| parallel["@folder"]
  choice -->|"モーダルなど"| intercept["(.)folder"]

開発者は状況に応じて適切な記法を選ぶ必要がありますが、選択肢が多いため迷いが生じます。

解決策

Next.js のルーティングを効率的に活用するには、早見表と図解による体系的な理解 が有効です。本記事では以下の方針で整理します。

解決の方針

  1. 記法を分類: セグメント、グループ、特殊機能の 3 つに分ける
  2. 用途を明確化: それぞれの記法が解決する問題を示す
  3. 実例を提示: 具体的なファイル構造と URL の対応を示す
  4. 図解で理解: フローチャートやディレクトリツリーで視覚化

次の図は、ルーティング記法の分類と用途をまとめたものです。

mermaidflowchart TD
  routing["Next.js<br/>ルーティング"]
  routing --> segment["セグメント"]
  routing --> group_feature["グループ・<br/>特殊機能"]
  routing --> file_conv["ファイル規約"]

  segment --> static_seg["通常フォルダ"]
  segment --> dynamic_seg["[slug]"]
  segment --> catch_seg["[...slug]"]
  segment --> optional_seg["[[...slug]]"]

  group_feature --> route_group["(group)"]
  group_feature --> parallel["@folder"]
  group_feature --> intercept["(.)folder"]

  file_conv --> page_file["page.js"]
  file_conv --> layout_file["layout.js"]
  file_conv --> loading_file["loading.js"]
  file_conv --> error_file["error.js"]
  file_conv --> route_file["route.js"]

この分類により、記法の役割が明確になり、適切な選択ができるようになります。

具体例

以下では、各記法の具体的な使い方をコード例と共に解説します。

動的セグメント [slug]

動的セグメントは、単一のパラメータを URL から受け取る場合に使用します。ブログ記事の個別ページなどで活用されます。

ディレクトリ構造

textapp/
  blog/
    [slug]/
      page.js

URL 例

  • ​/​blog​/​hello-world
  • ​/​blog​/​nextjs-routing

コード例(page.js)

typescript// app/blog/[slug]/page.js

export default function BlogPost({ params }) {
  // params.slug に "hello-world" などが入る
  return (
    <article>
      <h1>{params.slug} の記事</h1>
      <p>
        動的セグメントで取得したスラッグを表示しています。
      </p>
    </article>
  );
}

上記のコードでは、params.slug を使って URL パラメータを取得しています。

静的生成との組み合わせ

typescript// app/blog/[slug]/page.js

// 静的生成時にパスを事前生成
export async function generateStaticParams() {
  // データソースから記事一覧を取得
  const posts = await fetch(
    'https://api.example.com/posts'
  ).then((res) => res.json());

  // slug の配列を返す
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default function BlogPost({ params }) {
  return <h1>{params.slug}</h1>;
}

generateStaticParams を使うことで、ビルド時に全ページを事前生成できます。

キャッチオールセグメント [...slug]

複数の階層を持つパスを一括で受け取りたい場合に使用します。ドキュメントサイトの階層構造などに便利です。

ディレクトリ構造

textapp/
  docs/
    [...slug]/
      page.js

URL 例

  • ​/​docs​/​getting-started
  • ​/​docs​/​api​/​routing​/​dynamic

コード例(page.js)

typescript// app/docs/[...slug]/page.js

export default function DocsPage({ params }) {
  // params.slug は配列になる
  // /docs/api/routing → ["api", "routing"]
  const pathSegments = params.slug.join(' / ');

  return (
    <div>
      <h1>ドキュメント</h1>
      <p>現在のパス: {pathSegments}</p>
    </div>
  );
}

params.slug は配列形式で渡されるため、join などで整形できます。

オプショナルキャッチオール [[...slug]]

キャッチオールセグメントに加え、セグメントが存在しない場合も同じページで対応できます。

ディレクトリ構造

textapp/
  shop/
    [[...slug]]/
      page.js

URL 例

  • ​/​shopparams.slugundefined
  • ​/​shop​/​electronicsparams.slug["electronics"]
  • ​/​shop​/​electronics​/​phonesparams.slug["electronics", "phones"]

コード例(page.js)

typescript// app/shop/[[...slug]]/page.js

export default function ShopPage({ params }) {
  const category = params.slug
    ? params.slug.join(' > ')
    : 'すべて';

  return (
    <div>
      <h1>ショップ</h1>
      <p>カテゴリ: {category}</p>
    </div>
  );
}

オプショナルキャッチオールを使うことで、トップページと階層ページを一つのコンポーネントで管理できます。

ルートグループ (group)

ルートグループは、URL に影響を与えずにファイルを論理的に整理するための仕組みです。マーケティングページと管理画面でレイアウトを分けたい場合などに有効です。

ディレクトリ構造

textapp/
  (marketing)/
    about/
      page.js
    contact/
      page.js
    layout.js
  (dashboard)/
    profile/
      page.js
    settings/
      page.js
    layout.js

URL 例

  • ​/​about(marketing) は URL に含まれない
  • ​/​profile(dashboard) は URL に含まれない

コード例(layout.js)

typescript// app/(marketing)/layout.js

export default function MarketingLayout({ children }) {
  return (
    <div>
      <header>マーケティングヘッダー</header>
      {children}
      <footer>マーケティングフッター</footer>
    </div>
  );
}
typescript// app/(dashboard)/layout.js

export default function DashboardLayout({ children }) {
  return (
    <div>
      <aside>ダッシュボードサイドバー</aside>
      <main>{children}</main>
    </div>
  );
}

ルートグループを使うことで、同じ app​/​ 配下でも異なるレイアウトを適用できます。

次の図は、ルートグループによるレイアウト分離を示します。

mermaidflowchart TD
  app["app/"]
  app --> marketing["(marketing)/"]
  app --> dashboard["(dashboard)/"]

  marketing --> mLayout["layout.js<br/>(マーケティング用)"]
  marketing --> about["about/page.js<br/>URL: /about"]
  marketing --> contact["contact/page.js<br/>URL: /contact"]

  dashboard --> dLayout["layout.js<br/>(ダッシュボード用)"]
  dashboard --> profile["profile/page.js<br/>URL: /profile"]
  dashboard --> settings["settings/page.js<br/>URL: /settings"]

このように、グループごとに異なる layout.js を配置することで、UI 構造を柔軟に管理できます。

パラレルルート @folder

パラレルルートは、同じレイアウト内で複数のページを並行して表示する機能です。ダッシュボードのように、サイドバーとメインコンテンツを別々のルートで管理したい場合に使用します。

ディレクトリ構造

textapp/
  dashboard/
    @sidebar/
      page.js
    @main/
      page.js
    layout.js

コード例(layout.js)

typescript// app/dashboard/layout.js

export default function DashboardLayout({ sidebar, main }) {
  return (
    <div style={{ display: 'flex' }}>
      {/* @sidebar の内容 */}
      <aside>{sidebar}</aside>
      {/* @main の内容 */}
      <main>{main}</main>
    </div>
  );
}

コード例(@sidebar/page.js)

typescript// app/dashboard/@sidebar/page.js

export default function Sidebar() {
  return (
    <nav>
      <ul>
        <li>メニュー 1</li>
        <li>メニュー 2</li>
      </ul>
    </nav>
  );
}

コード例(@main/page.js)

typescript// app/dashboard/@main/page.js

export default function Main() {
  return (
    <div>
      <h1>ダッシュボード</h1>
      <p>メインコンテンツを表示します。</p>
    </div>
  );
}

パラレルルートを使うことで、複数のコンポーネントを独立したルートとして管理でき、コードの分離が進みます。

インターセプトルート (.)folder

インターセプトルートは、特定の URL にアクセスした際に、別のページをモーダルやオーバーレイで表示する機能です。画像ギャラリーで、サムネイルクリック時にモーダルで拡大表示する UI を実現できます。

ディレクトリ構造

textapp/
  photos/
    [id]/
      page.js
  @modal/
    (.)photos/
      [id]/
        page.js
  layout.js

インターセプト階層の記法

記法意味
(.)folder同じ階層
(..)folder1 つ上の階層
(..)(..)folder2 つ上の階層
(...)folderルート(app/)から

コード例(layout.js)

typescript// app/layout.js

export default function RootLayout({ children, modal }) {
  return (
    <html>
      <body>
        {children}
        {/* モーダルが存在する場合のみ表示 */}
        {modal}
      </body>
    </html>
  );
}

コード例(@modal/(.)photos/[id]/page.js)

typescript// app/@modal/(.)photos/[id]/page.js

export default function PhotoModal({ params }) {
  return (
    <div
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100vw',
        height: '100vh',
        background: 'rgba(0,0,0,0.8)',
      }}
    >
      <div
        style={{
          background: 'white',
          padding: '2rem',
          margin: '5rem auto',
          maxWidth: '600px',
        }}
      >
        <h2>写真 {params.id}</h2>
        <p>モーダル表示中</p>
      </div>
    </div>
  );
}

コード例(photos/[id]/page.js)

typescript// app/photos/[id]/page.js

export default function PhotoPage({ params }) {
  return (
    <div>
      <h1>写真 {params.id}</h1>
      <p>通常ページとして表示</p>
    </div>
  );
}

インターセプトルートを使うことで、同じ URL でもナビゲーションの文脈に応じて異なる UI を表示できます。

次の図は、インターセプトルートの動作フローを示します。

mermaidflowchart LR
  user["ユーザー"] -->|"クリック"| gallery["ギャラリーページ"]
  gallery -->|"内部遷移"| intercept["@modal/(.)photos/[id]<br/>(モーダル表示)"]
  user -->|"直接アクセス"| direct["photos/[id]<br/>(通常ページ)"]

内部遷移ではモーダル、直接アクセスでは通常ページが表示されるという使い分けができます。

ファイル規約の活用

App Router では、特定のファイル名に特別な役割が割り当てられています。

page.js

ルートとして公開される UI を定義します。このファイルがないディレクトリは URL としてアクセスできません。

typescript// app/about/page.js

export default function AboutPage() {
  return <h1>About ページ</h1>;
}

layout.js

複数のページで共有する UI レイアウトを定義します。ネストされた layout.js は親のレイアウトを継承します。

typescript// app/layout.js

export default function RootLayout({ children }) {
  return (
    <html lang='ja'>
      <body>
        <header>共通ヘッダー</header>
        {children}
        <footer>共通フッター</footer>
      </body>
    </html>
  );
}

loading.js

React Suspense を使ったローディング UI を定義します。ページのデータ取得中に表示されます。

typescript// app/dashboard/loading.js

export default function Loading() {
  return <p>読み込み中...</p>;
}

error.js

エラーバウンダリとして機能し、子コンポーネントでエラーが発生した場合に表示されます。

typescript// app/dashboard/error.js

'use client';

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

エラーバウンダリはクライアントコンポーネントである必要があるため、'use client' を指定します。

not-found.js

404 Not Found 時の UI を定義します。

typescript// app/not-found.js

export default function NotFound() {
  return (
    <div>
      <h1>404 - ページが見つかりません</h1>
      <p>お探しのページは存在しません。</p>
    </div>
  );
}

route.js

API エンドポイントを定義します。page.js と同じディレクトリには配置できません。

typescript// app/api/posts/route.js

export async function GET() {
  const posts = [
    { id: 1, title: 'Hello World' },
    { id: 2, title: 'Next.js Routing' },
  ];

  return Response.json(posts);
}
typescript// app/api/posts/route.js

export async function POST(request) {
  const body = await request.json();
  // データベースに保存する処理
  return Response.json({
    message: '作成しました',
    data: body,
  });
}

route.js を使うことで、Next.js アプリ内に API を簡単に構築できます。

まとめ

Next.js の App Router におけるルーティングは、ファイルシステムベースの直感的な設計と、柔軟な機能の組み合わせにより、複雑な UI 構造を宣言的に表現できます。

本記事で紹介した内容を以下にまとめます。

  • 動的セグメント ([slug], [...slug], [[...slug]]) を使い分けることで、柔軟なパラメータ取得が可能
  • ルートグループ ((group)) により、URL に影響を与えずにファイルを論理的に整理
  • パラレルルート (@folder) で、同一レイアウト内に複数のルートを並行表示
  • インターセプトルート ((.)folder) により、モーダルやオーバーレイなどの UI パターンをルーティングで実現
  • ファイル規約 (page.js, layout.js, loading.js, error.js, route.js など) を活用し、宣言的な UI 構築

これらの機能を理解し、早見表を参照しながら開発を進めることで、メンテナンス性が高く、拡張しやすい Next.js アプリケーションを構築できるでしょう。

関連リンク