パラレルルート (Parallel Routes)
パラレルルート (Parallel Routes) を使用すると、同じレイアウト内で複数のページを同時または条件付きでレンダリングできます。ダッシュボードやソーシャルサイトのフィードなど、アプリケーションの高度に動的なセクションに有用です。
例えば、ダッシュボードでは team
と analytics
ページを同時にレンダリングするためにパラレルルートを使用できます:

規約
スロット
パラレルルートは名前付きスロットを使用して作成されます。スロットは @folder
規約で定義されます。例えば、次のファイル構造は @analytics
と @team
の2つのスロットを定義しています:

スロットは共有親レイアウトにプロップスとして渡されます。上記の例では、app/layout.js
のコンポーネントが @analytics
と @team
スロットのプロップスを受け取り、children
プロップスと並行してレンダリングできます:
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
export default function Layout({ children, team, analytics }) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
ただし、スロットはルートセグメントではなく、URL構造に影響しません。例えば、/@analytics/views
の場合、@analytics
はスロットなのでURLは /views
になります。スロットは通常のページコンポーネントと組み合わされ、ルートセグメントに関連付けられた最終的なページを形成します。このため、同じルートセグメントレベルで静的と動的のスロットを同時に持つことはできません。1つのスロットが動的の場合、そのレベルのすべてのスロットは動的でなければなりません。
知っておくと良いこと:
children
プロップスはフォルダにマップする必要のない暗黙的なスロットです。つまりapp/page.js
はapp/@children/page.js
と同等です。
default.js
初期ロードやフルページリロード時にマッチしないスロットのフォールバックとしてレンダリングする default.js
ファイルを定義できます。
次のフォルダ構造を考えてみましょう。@team
スロットには /settings
ページがありますが、@analytics
にはありません。

/settings
にナビゲートすると、@team
スロットは /settings
ページをレンダリングし、@analytics
スロットの現在アクティブなページを維持します。
リフレッシュ時、Next.js は @analytics
に対して default.js
をレンダリングします。default.js
が存在しない場合、代わりに 404
がレンダリングされます。
さらに、children
は暗黙的なスロットなので、Next.js が親ページのアクティブな状態を回復できない場合に children
のフォールバックをレンダリングするためにも default.js
ファイルを作成する必要があります。
動作
デフォルトでは、Next.js は各スロットのアクティブな_状態_(またはサブページ)を追跡します。ただし、スロット内でレンダリングされるコンテンツはナビゲーションのタイプによって異なります:
- ソフトナビゲーション: クライアントサイドナビゲーション時、Next.js は部分レンダリングを実行し、スロット内のサブページを変更しながら、他のスロットのアクティブなサブページを維持します(現在のURLと一致しなくても)。
- ハードナビゲーション: フルページロード(ブラウザリフレッシュ)後、Next.js は現在のURLと一致しないスロットのアクティブな状態を判断できません。代わりに、マッチしないスロットに対して
default.js
ファイルをレンダリングし、default.js
が存在しない場合は404
をレンダリングします。
知っておくと良いこと:
- マッチしないルートに対する
404
は、意図しないページでパラレルルートを誤ってレンダリングしないようにするのに役立ちます。
例
useSelectedLayoutSegment(s)
の使用
useSelectedLayoutSegment
と useSelectedLayoutSegments
はどちらも parallelRoutesKey
パラメータを受け取り、スロット内のアクティブルートセグメントを読み取ることができます。
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
ユーザーが app/@auth/login
(またはURLバーの /login
)にナビゲートすると、loginSegment
は文字列 "login"
と等しくなります。
条件付きルート
パラレルルートを使用して、ユーザーロールなどの条件に基づいてルートを条件付きでレンダリングできます。例えば、/admin
または /user
ロールに対して異なるダッシュボードページをレンダリングする場合:

import { checkUserRole } from '@/lib/auth'
export default function Layout({
user,
admin,
}: {
user: React.ReactNode
admin: React.ReactNode
}) {
const role = checkUserRole()
return role === 'admin' ? admin : user
}
import { checkUserRole } from '@/lib/auth'
export default function Layout({ user, admin }) {
const role = checkUserRole()
return role === 'admin' ? admin : user
}
タブグループ
スロット内に layout
を追加して、ユーザーがスロットを独立してナビゲートできるようにできます。これはタブを作成するのに便利です。
例えば、@analytics
スロットには /page-views
と /visitors
の2つのサブページがあります。

@analytics
内に、2つのページ間でタブを共有するための layout
ファイルを作成します:
import Link from 'next/link'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Link href="/page-views">ページビュー</Link>
<Link href="/visitors">訪問者</Link>
</nav>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export default function Layout({ children }) {
return (
<>
<nav>
<Link href="/page-views">ページビュー</Link>
<Link href="/visitors">訪問者</Link>
</nav>
<div>{children}</div>
</>
)
}
モーダル
パラレルルートはインターセプトルートと組み合わせて、ディープリンクをサポートするモーダルを作成できます。これにより、モーダル構築時の一般的な課題を解決できます:
- モーダルコンテンツをURLで共有可能にする
- ページがリフレッシュされたときにコンテキストを保持し、モーダルを閉じない
- 前のルートに戻るのではなく、戻るナビゲーションでモーダルを閉じる
- 進むナビゲーションでモーダルを再開する
次のUIパターンを考えてみましょう。ユーザーはクライアントサイドナビゲーションを使用してレイアウトからログインモーダルを開くか、別の /login
ページにアクセスできます:

このパターンを実装するには、まずメインのログインページをレンダリングする /login
ルートを作成します。

import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
次に、@auth
スロット内に null
を返す default.js
ファイルを追加します。これにより、モーダルがアクティブでないときにレンダリングされなくなります。
export default function Default() {
return null
}
export default function Default() {
return null
}
@auth
スロット内で、/(.)login
フォルダを更新して /login
ルートをインターセプトします。<Modal>
コンポーネントとその子を /(.)login/page.tsx
ファイルにインポートします:
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
知っておくと良いこと:
- ルートをインターセプトするために使用される規約(例:
(.)
)は、ファイルシステム構造によって異なります。詳細はインターセプトルート規約を参照してください。- モーダル機能 (
<Modal>
) をモーダルコンテンツ (<Login>
) から分離することで、モーダル内のコンテンツ(例: フォーム)がサーバーコンポーネントであることを保証できます。詳細はクライアントとサーバーコンポーネントの交互使用を参照してください。
モーダルを開く
Next.js ルーターを活用してモーダルを開閉できます。これにより、モーダルが開いているときや前後にナビゲートするときにURLが正しく更新されます。
モーダルを開くには、@auth
スロットを親レイアウトにプロップスとして渡し、children
プロップスと一緒にレンダリングします。
import Link from 'next/link'
export default function Layout({
auth,
children,
}: {
auth: React.ReactNode
children: React.ReactNode
}) {
return (
<>
<nav>
<Link href="/login">モーダルを開く</Link>
</nav>
<div>{auth}</div>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export default function Layout({ auth, children }) {
return (
<>
<nav>
<Link href="/login">モーダルを開く</Link>
</nav>
<div>{auth}</div>
<div>{children}</div>
</>
)
}
ユーザーが <Link>
をクリックすると、/login
ページにナビゲートする代わりにモーダルが開きます。ただし、リフレッシュ時や初期ロード時、/login
にナビゲートするとメインのログインページに移動します。
モーダルを閉じる
router.back()
を呼び出すか、Link
コンポーネントを使用してモーダルを閉じることができます。
'use client'
import { useRouter } from 'next/navigation'
export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter()
return (
<>
<button
onClick={() => {
router.back()
}}
>
モーダルを閉じる
</button>
<div>{children}</div>
</>
)
}
'use client'
import { useRouter } from 'next/navigation'
export function Modal({ children }) {
const router = useRouter()
return (
<>
<button
onClick={() => {
router.back()
}}
>
モーダルを閉じる
</button>
<div>{children}</div>
</>
)
}
@auth
スロットをレンダリングすべきでないページから離れるために Link
コンポーネントを使用する場合、null
を返すコンポーネントにパラレルルートがマッチするようにする必要があります。例えば、ルートページに戻る場合、@auth/page.tsx
コンポーネントを作成します:
import Link from 'next/link'
export function Modal({ children }: { children: React.ReactNode }) {
return (
<>
<Link href="/">モーダルを閉じる</Link>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export function Modal({ children }) {
return (
<>
<Link href="/">モーダルを閉じる</Link>
<div>{children}</div>
</>
)
}
export default function Page() {
return null
}
export default function Page() {
return null
}
または、他のページ(/foo
、/foo/bar
など)にナビゲートする場合は、キャッチオールスロットを使用できます:
export default function CatchAll() {
return null
}
export default function CatchAll() {
return null
}
知っておくと良いこと:
- モーダルを閉じるために
@auth
スロットでキャッチオールルートを使用しているのは、パラレルルートの動作(#behavior)によるものです。スロットにマッチしなくなったルートへのクライアントサイドナビゲーションは表示されたままになるため、モーダルを閉じるにはnull
を返すルートにマッチさせる必要があります。- 他の例としては、ギャラリーで写真モーダルを開きながら専用の
/photo/[id]
ページを持つことや、サイドモーダルでショッピングカートを開くことが含まれます。- インターセプトとパラレルルートを使用したモーダルの例を参照してください。
ローディングとエラーUI
パラレルルートは独立してストリーミングできるため、各ルートに対して独立したエラーとローディング状態を定義できます:
