Next.js の Parallel Routes & Intercepting Routes を図解で理解する最新入門

Next.js の App Router に導入された Parallel Routes と Intercepting Routes は、現代の Web アプリケーション開発において革新的な機能です。これらの機能を理解することで、より複雑で高度なユーザーインターフェースを効率的に実装できるようになります。
本記事では、これらの新機能を図解を交えながら、初心者の方にもわかりやすく解説いたします。実際のコード例も豊富に用意しており、理論と実践の両面から理解を深めていただけます。
背景
App Router の登場とルーティングの進化
Next.js 13 で導入された App Router は、従来の Pages Router から大幅に進化したルーティングシステムです。この新しいシステムは、React Server Components やストリーミングなどの最新技術を活用し、より高速で効率的な Web アプリケーションの構築を可能にしています。
App Router では、ファイルシステムベースのルーティングが継続されながらも、新しい規約と機能が追加されました。特に注目すべきは、Parallel Routes と Intercepting Routes という 2 つの強力な機能です。
Next.js のルーティング進化を図で確認してみましょう。
mermaidflowchart TD
pages[Pages Router] -->|進化| app[App Router]
app --> server[Server Components]
app --> streaming[ストリーミング]
app --> parallel[Parallel Routes]
app --> intercepting[Intercepting Routes]
parallel --> ui1[複数UI同時表示]
intercepting --> ui2[URL インターセプト]
ui1 --> benefit1[ダッシュボード強化]
ui2 --> benefit2[モーダル制御向上]
この図が示すように、App Router は従来の機能を基盤としながら、新しい可能性を開拓しています。
従来のルーティングの課題と新機能の必要性
従来の Pages Router では、1 つの URL に対して 1 つのページコンポーネントが対応する単純な構造でした。この仕組みは理解しやすい反面、以下のような課題がありました。
課題項目 | 詳細内容 | 影響範囲 |
---|---|---|
単一ページ制約 | 1URL に 1 ページのみ対応 | UI 設計の制限 |
状態管理複雑化 | モーダルやサイドバーの状態管理が困難 | 開発効率低下 |
URL 構造制限 | 柔軟な URL 設計が困難 | SEO 対応の限界 |
これらの課題を解決するため、Next.js チームは新しいルーティング機能の開発に着手しました。その結果生まれたのが、Parallel Routes と Intercepting Routes です。
課題
複雑な UI 構成での画面レンダリングの課題
現代の Web アプリケーションでは、単一画面に複数の独立したコンテンツエリアを表示することが一般的になっています。例えば、ダッシュボード画面では以下のような要素が同時に表示されます。
複雑な UI 構成の課題を図解で理解しましょう。
mermaidflowchart LR
subgraph dashboard[ダッシュボード画面]
nav[ナビゲーション]
main[メインコンテンツ]
sidebar[サイドバー]
modal[モーダル]
end
subgraph problems[従来の課題]
state[状態管理複雑化]
render[レンダリング非効率]
url[URL構造制限]
end
dashboard --> problems
style problems fill:#ffcccc
従来のアプローチでは、これらの要素をすべて単一コンポーネント内で管理する必要があり、以下の問題が発生していました。
- 状態管理の複雑化: 各エリアの表示/非表示状態を親コンポーネントで一元管理
- レンダリングの非効率性: 一部エリアの更新で全体が再レンダリング
- コード保守性の低下: 機能追加時の影響範囲が不明確
モーダル表示や条件分岐ルーティングの実装困難
特に困難だったのが、モーダル表示の実装です。従来の手法では、以下のような課題がありました。
URL とモーダル状態の不一致 モーダルを開いた状態でブラウザの更新ボタンを押すと、モーダルが閉じてしまう問題がありました。これは、モーダルの状態が URL に反映されていないためです。
直接 URL アクセスの対応困難 ユーザーがモーダル表示状態の URL を直接ブラウザに入力した場合、適切にモーダルを表示することが困難でした。
条件分岐ルーティングの課題を以下の図で確認しましょう。
mermaidstateDiagram-v2
[*] --> リスト画面
リスト画面 --> モーダル表示: アイテムクリック
モーダル表示 --> リスト画面: モーダル閉じる
state "従来の問題" as problems {
URL反映なし
直接アクセス不可
状態管理複雑
}
モーダル表示 --> problems
これらの課題により、開発者は複雑な状態管理ライブラリを導入したり、独自のルーティング制御機能を実装したりする必要がありました。
解決策
Parallel Routes(並列ルート)の概念と仕組み
Parallel Routes は、同一画面内で複数の独立したルートを並列実行できる革新的な機能です。この機能により、各コンテンツエリアを独立したコンポーネントとして管理し、効率的なレンダリングが可能になります。
基本的な仕組み
Parallel Routes では、@folder
という命名規則を使用してスロットを定義します。
typescript// app/dashboard/layout.tsx
export default function Layout({
children,
analytics, // @analytics スロット
team, // @team スロット
user, // @user スロット
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
user: React.ReactNode;
}) {
return (
<div className='dashboard'>
{children}
<div className='analytics-section'>{analytics}</div>
<div className='team-section'>{team}</div>
<div className='user-section'>{user}</div>
</div>
);
}
このレイアウトコンポーネントは、複数のスロットを受け取り、それぞれを適切な位置に配置します。
ファイル構造とスロット定義
Parallel Routes を使用する際のディレクトリ構造は以下のようになります。
graphqlapp/
└── dashboard/
├── layout.tsx # レイアウトコンポーネント
├── page.tsx # メインコンテンツ
├── @analytics/ # アナリティクススロット
│ └── page.tsx
├── @team/ # チームスロット
│ └── page.tsx
└── @user/ # ユーザースロット
└── page.tsx
各スロットは独立してレンダリングされ、それぞれが独自のローディング状態やエラー処理を持つことができます。
Intercepting Routes(インターセプトルート)の概念と仕組み
Intercepting Routes は、特定のルートへのナビゲーションを「インターセプト(横取り)」して、別のコンポーネントを表示する機能です。これにより、モーダル表示や条件分岐ナビゲーションを elegant に実装できます。
インターセプト規則の理解
Intercepting Routes では、以下の規則を使用してインターセプト対象を指定します。
規則 | 意味 | 例 |
---|---|---|
(.) | 同じレベル | (.)/photo/[id] |
(..) | 1 つ上のレベル | (..)/photo/[id] |
(...) | 2 つ上のレベル | (...)/photo/[id] |
(....)+ | より上のレベル | (....)/photo/[id] |
モーダル実装でのインターセプト活用
画像ギャラリーでのモーダル表示を例に、Intercepting Routes の活用方法を見てみましょう。
typescript// app/gallery/@modal/(.)/photo/[id]/page.tsx
import { Modal } from '@/components/modal';
import { PhotoDetail } from '@/components/photo-detail';
export default function PhotoModal({
params,
}: {
params: { id: string };
}) {
return (
<Modal>
<PhotoDetail id={params.id} />
</Modal>
);
}
このコンポーネントは、/photo/[id]
へのナビゲーションをインターセプトし、モーダル形式で写真詳細を表示します。
Intercepting Routes の動作フローを図で確認しましょう。
mermaidsequenceDiagram
participant User as ユーザー
participant Gallery as ギャラリー画面
participant Router as Next.js Router
participant Modal as モーダル
participant PhotoPage as 写真詳細ページ
User->>Gallery: 写真をクリック
Gallery->>Router: /photo/123 へナビゲート
alt インターセプト発生
Router->>Modal: モーダル表示
Modal->>User: 写真をモーダルで表示
else 直接アクセス
Router->>PhotoPage: 通常ページ表示
PhotoPage->>User: 写真を専用ページで表示
end
この図が示すように、同じ URL でも アクセス方法によって異なるコンポーネントが表示されます。
2 つの機能の連携メカニズム
Parallel Routes と Intercepting Routes を組み合わせることで、より高度な UI パターンを実現できます。
統合アーキテクチャの設計
両機能を組み合わせた場合のアーキテクチャを図解します。
mermaidflowchart TD
subgraph app[アプリケーション]
subgraph layout[レイアウト]
main[メインコンテンツ]
sidebar[サイドバー]
modal[モーダルスロット]
end
subgraph routes[ルーティング]
parallel[Parallel Routes]
intercepting[Intercepting Routes]
end
end
parallel --> sidebar
parallel --> main
intercepting --> modal
style parallel fill:#e1f5fe
style intercepting fill:#f3e5f5
実装パターンの組み合わせ
以下は、ダッシュボードアプリケーションでの統合実装例です。
typescript// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
sidebar, // @sidebar Parallel Route
modal, // @modal Intercepting Route
}: {
children: React.ReactNode;
sidebar: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<div className='dashboard-layout'>
<aside className='sidebar'>{sidebar}</aside>
<main className='main-content'>
{children}
{modal} {/* モーダルをオーバーレイ表示 */}
</main>
</div>
);
}
この実装により、サイドバーは並列レンダリング、モーダルはインターセプトによる条件表示が実現されます。
具体例
Parallel Routes の実装例
実際のプロジェクトで Parallel Routes を活用したダッシュボード画面を構築してみましょう。
プロジェクト初期化とファイル構成
まず、必要なディレクトリ構造を作成します。
goapp/
└── dashboard/
├── layout.tsx
├── page.tsx
├── @analytics/
│ ├── page.tsx
│ ├── loading.tsx
│ └── error.tsx
├── @notifications/
│ ├── page.tsx
│ ├── loading.tsx
│ └── error.tsx
└── @activity/
├── page.tsx
├── loading.tsx
└── error.tsx
メインレイアウトの実装
ダッシュボードのレイアウトコンポーネントを実装します。
typescript// app/dashboard/layout.tsx
interface DashboardLayoutProps {
children: React.ReactNode;
analytics: React.ReactNode;
notifications: React.ReactNode;
activity: React.ReactNode;
}
export default function DashboardLayout({
children,
analytics,
notifications,
activity,
}: DashboardLayoutProps) {
return (
<div className='grid grid-cols-12 gap-6 p-6'>
{/* メインコンテンツエリア */}
<div className='col-span-8'>{children}</div>
{/* サイドバーエリア */}
<div className='col-span-4 space-y-6'>
{/* アナリティクス */}
<div className='bg-white rounded-lg shadow p-4'>
<h3 className='text-lg font-semibold mb-4'>
アナリティクス
</h3>
{analytics}
</div>
{/* 通知 */}
<div className='bg-white rounded-lg shadow p-4'>
<h3 className='text-lg font-semibold mb-4'>
通知
</h3>
{notifications}
</div>
{/* アクティビティ */}
<div className='bg-white rounded-lg shadow p-4'>
<h3 className='text-lg font-semibold mb-4'>
最近のアクティビティ
</h3>
{activity}
</div>
</div>
</div>
);
}
各スロットの実装
アナリティクススロットを実装します。
typescript// app/dashboard/@analytics/page.tsx
export default function AnalyticsSlot() {
return (
<div className='space-y-4'>
<div className='grid grid-cols-2 gap-4'>
<div className='text-center'>
<div className='text-2xl font-bold text-blue-600'>
1,234
</div>
<div className='text-sm text-gray-600'>
総売上
</div>
</div>
<div className='text-center'>
<div className='text-2xl font-bold text-green-600'>
567
</div>
<div className='text-sm text-gray-600'>
新規顧客
</div>
</div>
</div>
<div className='h-32 bg-gray-100 rounded flex items-center justify-center'>
<span className='text-gray-500'>グラフエリア</span>
</div>
</div>
);
}
通知スロットの実装です。
typescript// app/dashboard/@notifications/page.tsx
export default function NotificationsSlot() {
const notifications = [
{
id: 1,
message: '新しい注文が届きました',
time: '5分前',
},
{
id: 2,
message: '在庫が少なくなっています',
time: '1時間前',
},
{
id: 3,
message: '月次レポートが生成されました',
time: '2時間前',
},
];
return (
<div className='space-y-3'>
{notifications.map((notification) => (
<div
key={notification.id}
className='border-l-4 border-blue-500 pl-3'
>
<p className='text-sm font-medium'>
{notification.message}
</p>
<p className='text-xs text-gray-500'>
{notification.time}
</p>
</div>
))}
</div>
);
}
Intercepting Routes の実装例
次に、画像ギャラリーでモーダル表示を実現する Intercepting Routes を実装します。
ギャラリーの基本構造
bashapp/
├── gallery/
│ ├── layout.tsx
│ ├── page.tsx
│ └── @modal/
│ └── (.)/photo/
│ └── [id]/
│ └── page.tsx
└── photo/
└── [id]/
└── page.tsx
ギャラリーレイアウトの実装
typescript// app/gallery/layout.tsx
export default function GalleryLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<div className='relative'>
{children}
{modal}
</div>
);
}
ギャラリー一覧ページの実装
typescript// app/gallery/page.tsx
import Link from 'next/link';
import Image from 'next/image';
const photos = [
{ id: '1', src: '/photos/1.jpg', alt: '写真1' },
{ id: '2', src: '/photos/2.jpg', alt: '写真2' },
{ id: '3', src: '/photos/3.jpg', alt: '写真3' },
];
export default function GalleryPage() {
return (
<div className='container mx-auto px-4 py-8'>
<h1 className='text-3xl font-bold mb-8'>
フォトギャラリー
</h1>
<div className='grid grid-cols-3 gap-4'>
{photos.map((photo) => (
<Link
key={photo.id}
href={`/photo/${photo.id}`}
className='block relative aspect-square overflow-hidden rounded-lg hover:opacity-75 transition-opacity'
>
<Image
src={photo.src}
alt={photo.alt}
fill
className='object-cover'
/>
</Link>
))}
</div>
</div>
);
}
モーダル用インターセプトルートの実装
typescript// app/gallery/@modal/(.)/photo/[id]/page.tsx
'use client';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import { useEffect } from 'react';
export default function PhotoModal({
params,
}: {
params: { id: string };
}) {
const router = useRouter();
// ESCキーでモーダルを閉じる
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
router.back();
}
};
document.addEventListener('keydown', handleEsc);
return () =>
document.removeEventListener('keydown', handleEsc);
}, [router]);
return (
<div className='fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50'>
<div className='relative max-w-4xl max-h-full p-4'>
{/* 閉じるボタン */}
<button
onClick={() => router.back()}
className='absolute top-2 right-2 text-white text-2xl z-10 bg-black bg-opacity-50 rounded-full w-8 h-8 flex items-center justify-center'
>
×
</button>
{/* 写真表示エリア */}
<div className='relative'>
<Image
src={`/photos/${params.id}.jpg`}
alt={`写真 ${params.id}`}
width={800}
height={600}
className='rounded-lg'
/>
</div>
</div>
</div>
);
}
通常の写真詳細ページ
直接 URL アクセス時には、通常のページが表示されます。
typescript// app/photo/[id]/page.tsx
import Image from 'next/image';
import Link from 'next/link';
export default function PhotoPage({
params,
}: {
params: { id: string };
}) {
return (
<div className='container mx-auto px-4 py-8'>
<Link
href='/gallery'
className='inline-block mb-6 text-blue-600 hover:underline'
>
← ギャラリーに戻る
</Link>
<div className='max-w-4xl mx-auto'>
<Image
src={`/photos/${params.id}.jpg`}
alt={`写真 ${params.id}`}
width={800}
height={600}
className='w-full rounded-lg shadow-lg'
/>
<div className='mt-6 p-6 bg-white rounded-lg shadow'>
<h1 className='text-2xl font-bold mb-4'>
写真 {params.id}
</h1>
<p className='text-gray-600'>
この写真の詳細情報がここに表示されます。 直接URL
でアクセスした場合は、この専用ページが表示されます。
</p>
</div>
</div>
</div>
);
}
統合実装:画像ギャラリーとモーダル表示
最後に、Parallel Routes と Intercepting Routes を組み合わせた統合実装例を紹介します。
高度なダッシュボード実装
以下の実装では、ダッシュボードにフォトギャラリー機能を統合し、モーダル表示にも対応しています。
typescript// app/dashboard-advanced/layout.tsx
export default function AdvancedDashboardLayout({
children,
sidebar,
modal,
gallery,
}: {
children: React.ReactNode;
sidebar: React.ReactNode;
modal: React.ReactNode;
gallery: React.ReactNode;
}) {
return (
<div className='flex h-screen bg-gray-100'>
{/* サイドバー */}
<aside className='w-64 bg-white shadow-md'>
{sidebar}
</aside>
{/* メインコンテンツエリア */}
<main className='flex-1 overflow-auto'>
<div className='p-6'>{children}</div>
{/* ギャラリーセクション */}
<div className='p-6 border-t'>{gallery}</div>
</main>
{/* モーダルオーバーレイ */}
{modal}
</div>
);
}
Parallel Routes と Intercepting Routes の連携パターンを図で確認しましょう。
mermaidflowchart TD
subgraph browser[ブラウザ]
url[URL: /dashboard-advanced]
end
subgraph nextjs[Next.js App Router]
router[ルーター]
parallel[Parallel Routes処理]
intercept[Intercepting Routes処理]
end
subgraph components[コンポーネント]
layout[レイアウト]
sidebar[サイドバー]
main[メインコンテンツ]
gallery[ギャラリー]
modal[モーダル]
end
url --> router
router --> parallel
router --> intercept
parallel --> sidebar
parallel --> main
parallel --> gallery
intercept --> modal
layout --> components
この統合実装により、以下の機能が実現されます。
図で理解できる要点
- 複数のコンテンツエリアが独立してレンダリング
- モーダル表示が URL 状態と同期
- 直接アクセスとナビゲーションアクセスの使い分け
まとめ
新機能活用のメリットと今後の展望
Next.js の Parallel Routes と Intercepting Routes は、現代の Web アプリケーション開発における重要な課題を解決する革新的な機能です。
主要なメリット
メリット | 詳細 | 開発への影響 |
---|---|---|
独立レンダリング | 各コンテンツエリアが独立して更新 | パフォーマンス向上 |
URL 状態管理 | モーダルやサイドバーの状態が URL に反映 | SEO 対応・UX 向上 |
コード保守性 | 機能ごとの関心の分離 | 開発効率向上 |
直感的な実装 | ファイルシステムベースの設計 | 学習コストの削減 |
活用シーンの広がり
これらの機能は、以下のようなシーンで特に威力を発揮します。
- ダッシュボード系アプリケーション: 複数のデータ表示エリアの独立管理
- EC サイト: 商品一覧とモーダル詳細表示の連携
- SNS・メディアサイト: フィード表示とコンテンツ詳細のシームレス連携
- 管理画面: サイドバー・メインコンテンツ・モーダルの統合管理
今後の展望
Next.js チームは、これらの機能をさらに発展させる計画を発表しています。特に注目すべきは以下の点です。
パフォーマンスの最適化 Parallel Routes での並列データフェッチングのさらなる最適化が予定されています。
開発者体験の向上 TypeScript サポートの強化やデバッグツールの改善が進められています。
エコシステムとの連携 Storybook や Testing Library などの開発ツールとの連携強化も計画されています。
Next.js の新機能は、Web アプリケーション開発の未来を大きく変える可能性を秘めています。早期に習得することで、より高品質で保守性の高いアプリケーションを効率的に構築できるようになるでしょう。
これらの機能を活用して、ユーザーにとってより快適で、開発者にとってより扱いやすい Web アプリケーションを構築していきましょう。
関連リンク
- article
Next.js の Parallel Routes & Intercepting Routes を図解で理解する最新入門
- article
Convex × React/Next.js 最速連携:useQuery/useMutation の実践パターン
- article
Next.js の Middleware 活用法:リクエスト制御・認証・リダイレクトの実践例
- article
【解決策】Next.js での CORS エラーの原因と対処方法まとめ
- article
Next.js で環境変数を安全に管理するベストプラクティス
- article
shadcn/ui × Next.js:モダンな UI を爆速構築する方法
- article
【2025 年完全版】Remix の特徴・メリット・適用領域を総まとめ
- article
【2025 年最新】Convex の全体像を 10 分で理解:リアルタイム DB× 関数基盤の要点まとめ
- article
【2025 年最新版】Preact の強みと限界を実測で俯瞰:軽量・高速・互換性の現在地
- article
【2025 年最新】Playwright 入門:E2E テストの基本・特徴・できること完全ガイド
- article
【入門】GPT-5-Codex の使い方:セットアップから最初のプルリク作成まで完全ガイド
- article
Node.js の fetch 時代を理解する:undici 標準化で何が変わったのか
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来