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

Parallel Routes

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

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

Parallel Routes DiagramParallel Routes Diagram

スロット

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

Parallel Routes File-system StructureParallel Routes File-system Structure

スロットは共有親レイアウトにpropsとして渡されます。上記の例では、コンポーネントapp/layout.jsは現在@analytics@teamのスロット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}
</>
)
}

ただし、スロットはroute segmentsではなく、URL構造に影響を与えません。たとえば、/@analytics/viewsの場合、URLは@analyticsがスロットであるため/viewsになります。スロットは通常のPageコンポーネントと組み合わされて、route segmentに関連付けられた最終ページが形成されます。このため、同じroute segmentレベルでstaticなスロットとdynamicなスロットを別々に持つことはできません。1つのスロットが動的である場合、そのレベルのすべてのスロットも動的である必要があります。

Good to know:

  • children propは暗黙のスロットであり、フォルダーにマッピングする必要はありません。これはapp/page.jsapp/@children/page.jsと等価であることを意味します。

アクティブステートとナビゲーション

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

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

Good to know:

  • 一致しないルートのための404は、意図しないページにparallel routeをレンダリングしないことを保証するのに役立ちます。

default.js

初期の読み込みやフルページ再読み込み時に一致しないスロットのフォールバックとしてレンダリングするdefault.jsファイルを定義することができます。

次のフォルダー構造を考えてみましょう。@teamスロットには/settingsページがありますが、@analyticsにはありません。

Parallel Routes unmatched routesParallel Routes unmatched routes

/settingsにナビゲートすると、@teamスロットは/settingsページをレンダリングし、@analyticsスロットには現在アクティブなページがそのまま保持されます。

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

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

useSelectedLayoutSegment(s)

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

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}</>
}

タブグループ

スロット内にlayoutを追加して、ユーザーがスロットを独立してナビゲートできるようにすることができます。これはタブを作成するのに役立ちます。

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

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スロット内にdefault.jsファイルを追加し、それがアクティブでないときにはモーダルがレンダリングされないようにnullを返します。

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

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

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スロットをプロップとして渡し、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>
</>
)
}

@authスロットをレンダリングしなくなったページからナビゲートする場合に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 '...'
}

もしくは、他のページ(例: /foo/foo/barなど)にナビゲートする際には、catch-allスロットを使用することができます:

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

Good to know:

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

ローディングとエラーUI

Parallel Routesは独立してストリーミングでき、それぞれのルートに対して個別のエラーやローディングの状態を定義することが可能です:

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

詳細については、Loading UI および Error Handling のドキュメントを参照してください。