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

先日、2年間運用されているNext.jsのECサイトプロジェクトを引き継ぎました。app ディレクトリを開いた瞬間、画面を埋め尽くす60以上のフォルダに絶句しました。about、products、admin-users、marketing-campaign、shop-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/usersURLは以前のまま綺麗ですが、ファイル構造は一目瞭然です。新人が入ってきても、どこを触ればいいか迷いません。
使い道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.js の children としてレンダリングされます。
構造例:オンライン学習サイト
app/
├── layout.js # 全体:ヘッダー・フッター
└── courses/
├── layout.js # コースエリア:左側コース一覧メニュー
├── page.js # コース選択画面
└── [id]/
├── layout.js # 学習画面:右側進捗バー
└── page.js # 動画プレイヤーユーザーが /courses/123 にアクセスすると:
app/layout.js(ヘッダー)の中にcourses/layout.js(コース一覧)が描画され、その中にcourses/[id]/layout.js(進捗バー)が描画され、最後にcourses/[id]/page.js(動画)が表示されます。
各レイアウトは「自分が増やす差分UI」のことだけ気にすればOKです。
注意点
Layoutは再レンダリングされない
これが最大のメリットです。/courses/1 から /courses/2 に移動しても、親である app/layout.js や courses/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.jsdashboard/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>
)
}何が嬉しいのか?
- 並列ロード: 3つのスロットは並行してデータをフェッチします。
- 独立したLoading/Error: 各スロットフォルダに
loading.jsやerror.jsを置けます。「売上グラフ」がエラーになっても、「注文リスト」は正常に表示されます。 - 部分的なナビゲーション: 特定のスロットだけページ遷移させることも可能です(高度な使い方)。
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動作:
- ユーザーが
/productsで<Link href="/products/123">をクリック。 - Next.jsはURLを
/products/123に変えますが、画面遷移はせず、@modalスロット内の(.)products/[id]/page.jsをレンダリングします。 - ユーザーがページをリロードすると、インターセプトは無効になり、正規の
products/[id]/page.jsが表示されます。
これにより、共有可能なURLとシームレスなSPA体験を両立できます。
まとめ
これら4つの機能を組み合わせると、大規模アプリケーションの設計が劇的に改善します。
| 機能 | 役割 | キーワード |
|---|---|---|
| Route Groups | ディレクトリ整理、レイアウト分離 | (folder) |
| Nested Layouts | UI階層の再利用 | layout.js の入れ子 |
| Parallel Routes | 画面の分割ロード(ダッシュボード等) | @folder |
| Intercepting Routes | URL対応モーダル(Instagram風) | (.)folder |
まずは Route Groups でディレクトリを整理することから始めてみてください。それだけでプロジェクトの見通しが随分良くなるはずです。
Next.js 高度なルーティングのリファクタリング手順
Route Groups、Nested Layouts、Parallel Routes、Intercepting Routesを使った段階的な改善フロー
⏱️ Estimated time: 4 hr
- 1
Step1: プロジェクト構造の分析
現在の app フォルダを確認し、フォルダ数が20を超えている場合や、機能エリア(マーケティング、ショップ、管理画面など)が混在している場合はRoute Groupsの導入を検討します。 - 2
Step2: Route Groupsによる分割
機能ごとに (marketing), (shop), (dashboard) などのフォルダを作成し、既存のページを移動させます。各グループに必要な layout.js を作成し、個別のデザインを適用します。 - 3
Step3: Nested Layoutsの適用
深い階層のページ(例:ブログ記事、商品詳細)で共通のUI(サイドバーなど)がある場合、その階層に layout.js を追加してUIを共通化します。 - 4
Step4: Parallel Routesの実装(必要に応じて)
ダッシュボード等で複数の独立したウィジェットを表示したい場合、@slot フォルダを作成し、layout.js で props として受け取って配置します。 - 5
Step5: Intercepting Routesでのモーダル実装
一覧画面から詳細画面への遷移をモーダル化したい場合、Parallel Route (@modal) と Intercepting Route ((.)path) を組み合わせて実装します。
FAQ
Route Groupsを使うとURLの階層は変わりますか?
Parallel Routesのスロットが表示されません(404になる)。
Intercepting Routesでモーダルが出ず、通常のページ遷移になってしまいます。
4 min read · 公開日: 2025年12月18日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践

Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド

Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド


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