メインコンテンツまでスキップ

Parallel Routes

Parallel Routesを使用すると、同じレイアウト内で1つ以上のページを同時または条件付きでレンダリングできます。これは、ソーシャルサイトのダッシュボードやフィードなど、アプリの高度に動的なセクションで役立ちます。

たとえば、ダッシュボードを考えると、teamページとanalyticsページを同時にレンダリングするためにparallel routesを使用できます:

Parallel Routes DiagramParallel Routes Diagram

Slots

Parallel routesは、名前付きslotsを使用して作成されます。slotsは@folder規約で定義されます。たとえば、次のファイル構造では、@analytics@teamの2つのslotsが定義されています:

Parallel Routes File-system StructureParallel Routes File-system Structure

Slotsは共通の親レイアウトにpropsとして渡されます。上記の例では、app/layout.jsのコンポーネントは、現在@analytics@teamのslots propsを受け入れ、それらをchildren propと一緒に並行してレンダリングできます:

app/layout.tsx
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}

ただし、slotsはルートセグメントではなく、URL構造には影響しません。たとえば、/@analytics/viewsの場合、@analyticsはslotであるため、URLは/viewsになります。Slotsは通常のページコンポーネントと組み合わせて、ルートセグメントに関連付けられた最終ページを形成します。このため、同じルートセグメントレベルで静的slotと動的slotを別々に持つことはできません。1つのslotが動的である場合、そのレベルのすべてのslotは動的でなければなりません。

Good to know:

  • children propはマッピングの必要がない暗黙のslotです。つまり、app/page.jsapp/@children/page.jsと同等です。

アクティブ状態とナビゲーション

デフォルトでは、Next.jsは各slotのアクティブな状態(またはサブページ)を追跡します。ただし、slot内にレンダリングされるコンテンツはナビゲーションの種類によります:

  • ソフトナビゲーション:クライアントサイドナビゲーション中、Next.jsは部分的レンダリングを実行し、現在のURLと一致しなくても他のslotのアクティブなサブページを維持しながら、slot内のサブページを変更します。
  • ハードナビゲーション:フルページのロード(ブラウザのリフレッシュ)後、Next.jsは現在のURLと一致しないslotのアクティブ状態を判断できません。その代わりに、一致しないslotに対してdefault.jsファイルをレンダリングするか、default.jsが存在しない場合は404をレンダリングします。

Good to know:

  • 一致しないルートの404は、意図しないページにparallel routeがレンダリングされないようにするのに役立ちます。

default.js

初期読み込みまたはフルページリロード時に一致しないslotに対してフォールバックとしてレンダリングするためのdefault.jsファイルを定義できます。

次のフォルダ構造を考えます。@team slotには/settingsページがありますが、@analyticsにはありません。

Parallel Routes unmatched routesParallel Routes unmatched routes

/settingsにナビゲートすると、@team slotは/settingsページをレンダリングし、@analytics slotの現在アクティブなページを維持します。

リフレッシュ時、Next.jsは@analyticsに対してdefault.jsをレンダリングします。default.jsが存在しない場合は、代わりに404がレンダリングされます。

さらに、childrenは暗黙のslotであるため、Next.jsが親ページのアクティブな状態を回復できない場合に備えて、children用にフォールバックをレンダリングするdefault.jsファイルを作成する必要があります。

useSelectedLayoutSegment(s)

useSelectedLayoutSegmentuseSelectedLayoutSegmentsは両方ともparallelRoutesKeyパラメータを受け入れ、slot内のアクティブなルートセグメントを読み取ることができます。

app/layout.tsx
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}

ユーザーがapp/@auth/login(またはURLバーの/login)にナビゲートすると、loginSegmentは文字列"login"と等しくなります。

条件付きルート

Parallel Routesを使用して、ユーザーの役割などの特定の条件に基づいてルートを条件付きでレンダリングすることができます。たとえば、/adminまたは/user役割の異なるダッシュボードページを表示するために:

Conditional routes diagramConditional routes diagram
app/dashboard/layout.tsx
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
}

タブグループ

ユーザーがslotを独立してナビゲートできるようにするために、slot内にlayoutを追加できます。これはタブを作成するのに便利です。

たとえば、@analytics slotには/page-views/visitorsの2つのサブページがあります。

Analytics slot with two subpages and a layoutAnalytics slot with two subpages and a layout

@analytics内で、タブを2つのページ間で共有するためにlayoutファイルを作成します:

app/@analytics/layout.tsx
import Link from 'next/link'

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Link href="/page-views">Page Views</Link>
<Link href="/visitors">Visitors</Link>
</nav>
<div>{children}</div>
</>
)
}

モーダル

Parallel Routesは、Intercepting Routesと組み合わせることで、ディープリンクをサポートするモーダルを作成できます。これにより、モーダルを構築する際の一般的な課題を解決できます:

  • モーダルのコンテンツをURLを通じて共有可能にする
  • ページをリフレッシュしたときにモーダルを閉じるのではなく、コンテキストを保持する
  • 戻るナビゲーションでモーダルを閉じるのではなく、前のルートに戻る
  • 進むナビゲーションでモーダルを再度開く

以下のUIパターンを検討します。ここでは、ユーザーがクライアントサイドナビゲーションを使用してレイアウトからログインモーダルを開くか、別の/loginページにアクセスできます:

Parallel Routes DiagramParallel Routes Diagram

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

Parallel Routes DiagramParallel Routes Diagram
app/login/page.tsx
import { Login } from '@/app/ui/login'

export default function Page() {
return <Login />
}

次に、@auth slot内にdefault.jsファイルを追加し、nullを返します。これにより、モーダルがアクティブでないときにレンダリングされないようにします。

app/@auth/default.tsx
export default function Default() {
return null
}

@auth slot内で、/(.)loginフォルダを更新して/loginルートをインターセプトします。<(Modal)>コンポーネントとその子要素を/(.)login/page.tsxファイルにインポートします:

app/@auth/(.)login/page.tsx
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}

Good to know:

モーダルを開く

これで、Next.js routerを利用してモーダルを開閉できます。これにより、モーダルが開いているときや、前後にナビゲートするときにURLが正しく更新されます。

モーダルを開くには、親レイアウトに@auth slotをpropとして渡し、children propと一緒にレンダリングします。

app/layout.tsx
import Link from 'next/link'

export default function Layout({
auth,
children,
}: {
auth: React.ReactNode
children: React.ReactNode
}) {
return (
<>
<nav>
<Link href="/login">Open modal</Link>
</nav>
<div>{auth}</div>
<div>{children}</div>
</>
)
}

ユーザーが<Link>をクリックすると、/loginページに遷移する代わりにモーダルが開きます。ただし、リフレッシュ時や初期読み込み時には、/loginに遷移するとメインのログインページが表示されます。

モーダルを閉じる

router.back()を呼び出すか、Linkコンポーネントを使用してモーダルを閉じることができます。

app/ui/modal.tsx
'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter()

return (
<>
<button
onClick={() => {
router.back()
}}
>
Close modal
</button>
<div>{children}</div>
</>
)
}

モーダルをレンダリングするべきでないページからナビゲートする際にLinkコンポーネントを使用する場合、parallel routeがnullを返すコンポーネントに一致することを確認する必要があります。たとえば、ルートページに戻るときには、@auth/page.tsxコンポーネントを作成します:

app/ui/modal.tsx
import Link from 'next/link'

export function Modal({ children }: { children: React.ReactNode }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
app/@auth/page.tsx
export default function Page() {
return null
}

または、他のページ(/foo/foo/barなど)にナビゲートする場合、catch-all slotを使用できます:

app/@auth/[...catchAll]/page.tsx
export default function CatchAll() {
return null
}

Good to know:

  • モーダルを閉じるために@auth slotでcatch-allルートを使用するのは、アクティブ状態とナビゲーションで説明した動作のためです。slotと一致しないルートへのクライアントサイドナビゲーションは表示されたままになるため、モーダルを閉じるためには、slotをnullを返すルートに一致させる必要があります。
  • 他の例としては、ギャラリーで写真モーダルを開きつつ、専用の/photo/[id]ページを持つことや、サイドモーダルでショッピングカートを開くことが含まれます。
  • Intercepted RoutesおよびParallel Routesを使用したモーダルの例を見る

ローディングとエラーUI

Parallel Routesは独立してストリームされるため、各ルートに対して独立したエラーとローディング状態を定義できます:

Parallel routes enable custom error and loading statesParallel routes enable custom error and loading states

詳細はローディングUIエラー処理ドキュメントを参照してください。