言語を切り替える
テーマを切り替える

Next.js 高度なルーティング完全ガイド:Route Groups、ネストされたレイアウト、Parallel Routes、Intercepting Routes

先日、2年間運用されているNext.jsのECサイトプロジェクトを引き継ぎました。app ディレクトリを開いた瞬間、画面を埋め尽くす60以上のフォルダに絶句しました。aboutproductsadmin-usersmarketing-campaignshop-cart……すべてがフラットに並んでいました。ユーザー関連のページを探すのにも一苦労。さらに最悪なのは、3人の開発者が同時にルーティングファイルをいじるため、Gitの競合が毎日発生し、コードレビューだけで30分かかる状態でした。

フォルダの山を前にして思い出しました。「Next.jsにはRoute GroupsやParallel Routesみたいな機能があったはずだ」。公式ドキュメントを見返すと、これらの機能はNext.js 13から存在していたのに、全く使われていませんでした。ツールが足りないのではなく、いつ何をどう使うべきかが知られていなかったのです。

その夜、私は3時間かけてこれらの機能を再学習し、2日かけて構造をリファクタリングしました。結果、ディレクトリ構造は劇的にスッキリし、チーム内での「競合した!」という悲鳴も消えました。そして私もようやく、これら4つの機能(Route Groups、Nested Layouts、Parallel Routes、Intercepting Routes)が何を解決するためにあるのかを理解しました。

この記事では、プロジェクトが大規模化し、混沌としてきた際に役立つ、これら4つの高度なルーティング機能の「使いどころ」と「実装方法」、そして「ハマりどころ」を解説します。

Route Groups (ルートグループ) - ディレクトリを整理整頓

Route Groupsとは?

結論から言うと、フォルダ名を (marketing)(shop) のように丸括弧で囲む機能です。魔法のような点は、この括弧付きフォルダ名はURLに含まれないことです。

一見地味ですが、リアルな開発現場では強力です。

例えばECサイトには、「マーケティングページ(トップ、About)」、「ショップ機能(商品一覧、カート)」、「管理画面(注文管理、ユーザー管理)」があります。従来の方法では、これらを app 直下に並べるか、無理やり /shop/products のようにURL階層を深くするしかありませんでした。しかし、/marketing/about なんてURLにはしたくないですよね?

Route Groupsを使えば、URLを変えずに、ファイルシステム上だけでページを分類できます。

3つの核心的な使い道

使い道1:チームや機能ごとのディレクトリ分割

60個のフラットなフォルダを、機能ごとに3つに分けられます:

app/
├── (marketing)/    # マーケティングチーム担当
│   ├── page.js     # トップページ → yoursite.com/
│   ├── about/      # About → yoursite.com/about
│   └── pricing/    # 価格 → yoursite.com/pricing
├── (shop)/         # ショップフロント担当
│   ├── products/   # 商品 → yoursite.com/products
│   └── cart/       # カート → yoursite.com/cart
└── (dashboard)/    # バックエンド担当
│   ├── orders/     # 注文 → yoursite.com/orders
│   └── users/      # ユーザー → yoursite.com/users

URLは以前のまま綺麗ですが、ファイル構造は一目瞭然です。新人が入ってきても、どこを触ればいいか迷いません。

使い道2:エリアごとに異なるルートレイアウトを適用

これが最強の機能です。マーケティングページと管理画面のナビゲーションバーを分けたい場合、どうしますか? 普通はコンポーネント内で分岐処理を書いたりしますが、Route Groupsならもっとエレガントです。

各グループに独自の layout.js を持たせることができます。

app/
├── (marketing)/
│   ├── layout.js        # マーケティング用:派手なヘッダー + フッター
│   └── ...
├── (shop)/
│   ├── layout.js        # ショップ用:カートアイコン付きヘッダー
│   └── ...
└── (dashboard)/
    ├── layout.js        # 管理画面用:サイドバー + 認証チェック
    └── ...

これで、完全に独立したレイアウトを持つ3つのアプリが、1つのNext.jsプロジェクトに共存できます。

使い道3:特定ページのみレイアウトを適用

例えば、「ブログ記事全体にはサイドバーをつけたいが、ブログトップにはつけたくない」といった場合。

app/
├── blog/
│   ├── page.js         # ブログトップ(サイドバーなし)
│   └── (articles)/     # 記事グループ
│       ├── layout.js   # ここでサイドバーを定義
│       ├── [slug]/     # 記事詳細 → /blog/xxx
│       └── ...

(articles) はURLに出てきませんが、この中のページだけに layout.js が適用されます。

リファクタリング実例:カオスから秩序へ

冒頭のプロジェクトをリファクタリングした結果です。

Before(カオス):

app/
├── page.js
├── about/
├── pricing/
├── products/
├── cart/
├── admin-orders/
├── admin-users/
├── ...(他50個)

After(スッキリ):

app/
├── (marketing)/
│   ├── layout.js
│   ├── page.js
│   ├── about/
│   └── pricing/
├── (shop)/
│   ├── layout.js
│   ├── products/
│   ├── cart/
└── (dashboard)/
    ├── layout.js
    ├── orders/
    └── users/

3つの落とし穴

罠1:URLの衝突
URLからグループ名が消えるため、別グループでも同じパスを作ると重複エラーになります。
(例:(marketing)/about(shop)/about はどちらも /about になるためNG)。

罠2:ルートレイアウト間の移動はフルリロードになる
(shop) のページから (marketing) のページへ移動する場合、根っこのレイアウトが違うため、Next.jsはページ全体をリロード(Full Page Load)します。SPAのような遷移にはなりません。これは意図的な設計ですが、知っておく必要があります。

罠3:page.js の位置
トップページ(/)の page.js は、app/page.js に置くこともできますが、複数のルートレイアウトがある場合は、どれか1つのグループ(例:(marketing)/page.js)に入れる必要があります。


Nested Layouts (ネストされたレイアウト) - 構造の再利用

何を解決するのか?

「トップページはヘッダーのみ」「ブログ一覧はヘッダー+左サイドバー」「記事詳細はヘッダー+左サイドバー+右目次」。
このようにUIが積み重なっていく要件はよくあります。これを各ページでコンポーネントとして配置すると、修正が大変です。

Nested Layouts(入れ子のレイアウト)は、レイアウトをマトリョーシカのように重ねていく機能です。

仕組み

子フォルダの layout.js は、親フォルダの layout.jschildren としてレンダリングされます。

構造例:オンライン学習サイト

app/
├── layout.js              # 全体:ヘッダー・フッター
└── courses/
    ├── layout.js          # コースエリア:左側コース一覧メニュー
    ├── page.js            # コース選択画面
    └── [id]/
        ├── layout.js      # 学習画面:右側進捗バー
        └── page.js        # 動画プレイヤー

ユーザーが /courses/123 にアクセスすると:

  1. app/layout.js(ヘッダー)の中に
  2. courses/layout.js(コース一覧)が描画され、その中に
  3. courses/[id]/layout.js(進捗バー)が描画され、最後に
  4. courses/[id]/page.js(動画)が表示されます。

各レイアウトは「自分が増やす差分UI」のことだけ気にすればOKです。

注意点

Layoutは再レンダリングされない
これが最大のメリットです。/courses/1 から /courses/2 に移動しても、親である app/layout.jscourses/layout.js は再マウントされません。つまり、スクロール位置や検索ボックスの入力状態が維持されます


Parallel Routes (並行ルート) - 複数画面の同時表示

どんな時に使う?

管理画面のダッシュボードを想像してください。「売上グラフ」「最近の注文」「在庫アラート」。
これら3つを1つのページに表示したい場合、従来は dashboard/page.js で3つのコンポーネントを読み込んでいました。しかし、どれか1つのAPIが遅いと画面全体が待たされるか、複雑な ローディング制御が必要でした。

Parallel Routesを使えば、1つのページ内で複数の「独立したページ(スロット)」を同時にロード・表示できます。

構文:@スロット名

フォルダ名に @ を付けるとスロット(Slot)になります。

app/
└── dashboard/
    ├── layout.js          # ここでスロットを受け取る
    ├── @sales/            # スロット1:売上
    │   └── page.js
    ├── @orders/           # スロット2:注文
    │   └── page.js
    └── @inventory/        # スロット3:在庫
        └── page.js

dashboard/layout.js では、これらをpropsとして受け取ります:

export default function DashboardLayout({
  children,    // dashboard/page.js の中身
  sales,       // @sales/page.js
  orders,      // @orders/page.js
  inventory    // @inventory/page.js
}) {
  return (
    <div className="grid grid-cols-2 gap-4">
      <div className="col-span-2">{children}</div>
      <div className="bg-white p-4">{sales}</div>
      <div className="bg-white p-4">{orders}</div>
      <div className="bg-white p-4">{inventory}</div>
    </div>
  )
}

何が嬉しいのか?

  1. 並列ロード: 3つのスロットは並行してデータをフェッチします。
  2. 独立したLoading/Error: 各スロットフォルダに loading.jserror.js を置けます。「売上グラフ」がエラーになっても、「注文リスト」は正常に表示されます。
  3. 部分的なナビゲーション: 特定のスロットだけページ遷移させることも可能です(高度な使い方)。

Tips: default.js を忘れずに

もし /dashboard/settings に移動したとき、@sales などのスロットには何を表示すべきでしょうか? 対応するものがないとNext.jsは困ってしまいます(404になります)。
そこで default.js を置きます。通常は return null しておけば、ルート不一致のときにそのスロットを非表示にできます。


Intercepting Routes (インターセプト・ルート) - モーダルの革命

InstagramのUXを再現する

InstagramやTwitterで画像をクリックすると、詳細がモーダルで開きますが、URLは個別の詳細ページ(/p/xxx)に変わります

  • ここでリロードすると? → モーダルではなく、ちゃんとした詳細ページが表示されます。
  • ここで「戻る」を押すと? → モーダルが閉じて、元のフィードに戻ります。

これを実現するのが Intercepting Routes です。「ページ遷移を横取り(インターセプト)して、別のコンポーネント(モーダル等)を表示する」機能です。

構文:(..)

相対パスのように記述します:

  • (.) : 同じ階層をインターセプト
  • (..) : 一つ上の階層をインターセプト
  • (...) : ルート(app)からインターセプト

実装例:商品一覧からクイックビュー

商品一覧 (/products) から商品をクリックしたとき、通常は詳細ページ (/products/123) に遷移しますが、それを横取りしてモーダルを出します。

ディレクトリ構造

app/
├── products/
│   ├── page.js           # 一覧ページ
│   └── [id]/
│       └── page.js       # 通常の詳細ページ(リロード時用)

└── @modal/               # Parallel Routeを使用
    ├── (.)products/      # インターセプト!
    │   └── [id]/
    │       └── page.js   # モーダルの中身
    └── default.js

動作

  1. ユーザーが /products<Link href="/products/123"> をクリック。
  2. Next.jsはURLを /products/123 に変えますが、画面遷移はせず、@modal スロット内の (.)products/[id]/page.js をレンダリングします。
  3. ユーザーがページをリロードすると、インターセプトは無効になり、正規の products/[id]/page.js が表示されます。

これにより、共有可能なURLシームレスなSPA体験を両立できます。


まとめ

これら4つの機能を組み合わせると、大規模アプリケーションの設計が劇的に改善します。

機能役割キーワード
Route Groupsディレクトリ整理、レイアウト分離(folder)
Nested LayoutsUI階層の再利用layout.js の入れ子
Parallel Routes画面の分割ロード(ダッシュボード等)@folder
Intercepting RoutesURL対応モーダル(Instagram風)(.)folder

まずは Route Groups でディレクトリを整理することから始めてみてください。それだけでプロジェクトの見通しが随分良くなるはずです。

Next.js 高度なルーティングのリファクタリング手順

Route Groups、Nested Layouts、Parallel Routes、Intercepting Routesを使った段階的な改善フロー

⏱️ Estimated time: 4 hr

  1. 1

    Step1: プロジェクト構造の分析

    現在の app フォルダを確認し、フォルダ数が20を超えている場合や、機能エリア(マーケティング、ショップ、管理画面など)が混在している場合はRoute Groupsの導入を検討します。
  2. 2

    Step2: Route Groupsによる分割

    機能ごとに (marketing), (shop), (dashboard) などのフォルダを作成し、既存のページを移動させます。各グループに必要な layout.js を作成し、個別のデザインを適用します。
  3. 3

    Step3: Nested Layoutsの適用

    深い階層のページ(例:ブログ記事、商品詳細)で共通のUI(サイドバーなど)がある場合、その階層に layout.js を追加してUIを共通化します。
  4. 4

    Step4: Parallel Routesの実装(必要に応じて)

    ダッシュボード等で複数の独立したウィジェットを表示したい場合、@slot フォルダを作成し、layout.js で props として受け取って配置します。
  5. 5

    Step5: Intercepting Routesでのモーダル実装

    一覧画面から詳細画面への遷移をモーダル化したい場合、Parallel Route (@modal) と Intercepting Route ((.)path) を組み合わせて実装します。

FAQ

Route Groupsを使うとURLの階層は変わりますか?
いいえ、変わりません。`(folderName)` という形式のフォルダ名は、Next.jsのルーティングシステムによってURLパスから除外されます。
Parallel Routesのスロットが表示されません(404になる)。
遷移先のルートに対応するスロットのページが存在しない場合、Next.jsは404を返します。これを防ぐために、各スロットフォルダ(または上位)に `default.js` を作成し、デフォルトの表示内容(または `return null`)を定義してください。
Intercepting Routesでモーダルが出ず、通常のページ遷移になってしまいます。
インターセプトは「クライアントサイドナビゲーション(<Link>タグでの遷移)」でのみ発生します。ブラウザのアドレスバーに直接入力したり、リロードした場合はインターセプトされず、通常のページが表示されます。これが仕様です。

4 min read · 公開日: 2025年12月18日 · 更新日: 2026年1月22日

コメント

GitHubアカウントでログインしてコメントできます

関連記事