パラレルルーティング (Parallel Routes)

パラレルルーティング (Parallel Routing) を使用すると、同一レイアウト内で複数のページを同時または条件付きでレンダリングできます。ダッシュボードやソーシャルサイトのフィードなど、アプリケーションの高度に動的なセクションにおいて、複雑なルーティングパターンを実装する際に有用です。

例えば、チームページと分析ページを同時にレンダリングできます。

パラレルルーティングの図解

パラレルルーティングでは、各ルートが独立してストリーミングされる際に、個別のエラー状態やローディング状態を定義できます。

パラレルルーティングによるカスタムエラーとローディング状態

また、認証状態などの条件に基づいてスロットを条件付きでレンダリングすることも可能です。これにより、同一URL上で完全に分離されたコードを実現できます。

条件付きルーティングの図解

規約

パラレルルートは名前付きスロットを使用して作成されます。スロットは @folder 規約で定義され、同じ階層のレイアウトにプロパティとして渡されます。

スロットはルートセグメントではなく、URL構造に影響しません。/@team/members というファイルパスは /members でアクセス可能です。

例えば、以下のファイル構造では @analytics@team の2つの明示的なスロットが定義されています。

パラレルルーティングのファイルシステム構造

上記のフォルダ構造により、app/layout.js のコンポーネントは @analytics@team のスロットプロパティを受け取り、children プロパティと並行してレンダリングできます:

export default function Layout(props: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {props.children}
      {props.team}
      {props.analytics}
    </>
  )
}
export default function Layout(props) {
  return (
    <>
      {props.children}
      {props.team}
      {props.analytics}
    </>
  )
}

補足: children プロパティはフォルダにマップする必要のない暗黙的なスロットです。つまり app/page.jsapp/@children/page.js と同等です。

不一致ルート

デフォルトでは、スロット内にレンダリングされるコンテンツは現在のURLと一致します。

スロットが一致しない場合、Next.jsがレンダリングするコンテンツはルーティング技術とフォルダ構造によって異なります。

default.js

Next.jsが現在のURLに基づいてスロットのアクティブ状態を回復できない場合、フォールバックとして default.js ファイルを定義できます。

以下のフォルダ構造を考えてみましょう。@team スロットには settings ディレクトリがありますが、@analytics にはありません。

パラレルルーティングの不一致ルート

ナビゲーション時

ナビゲーション時、Next.jsは現在のURLと一致しなくても、スロットの以前のアクティブ状態をレンダリングします。

リロード時

リロード時、Next.jsはまず不一致スロットの default.js ファイルをレンダリングしようとします。利用できない場合、404がレンダリングされます。

不一致ルートに対する404は、意図せず並行レンダリングすべきでないルートをレンダリングしないようにするために役立ちます。

useSelectedLayoutSegment(s)

useSelectedLayoutSegmentuseSelectedLayoutSegments はどちらも parallelRoutesKey を受け取り、そのスロット内のアクティブルートセグメントを読み取ることができます。

'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default async function Layout(props: {
  //...
  auth: React.ReactNode
}) {
  const loginSegments = useSelectedLayoutSegment('auth')
  // ...
}
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default async function Layout(props) {
  const loginSegments = useSelectedLayoutSegment('auth')
  // ...
}

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

モーダル

パラレルルーティングを使用してモーダルをレンダリングできます。

パラレルルーティングのモーダル図解

@auth スロットは <Modal> コンポーネントをレンダリングし、/login などの一致するルートにナビゲートすることで表示できます。

export default async function Layout(props: {
  // ...
  auth: React.ReactNode
}) {
  return (
    <>
      {/* ... */}
      {props.auth}
    </>
  )
}
export default async function Layout(props) {
  return (
    <>
      {/* ... */}
      {props.auth}
    </>
  )
}
import { Modal } from 'components/modal'

export default function Login() {
  return (
    <Modal>
      <h1>ログイン</h1>
      {/* ... */}
    </Modal>
  )
}
import { Modal } from 'components/modal'

export default function Login() {
  return (
    <Modal>
      <h1>ログイン</h1>
      {/* ... */}
    </Modal>
  )
}

モーダルがアクティブでないときにコンテンツがレンダリングされないようにするには、null を返す default.js ファイルを作成します。

export default function Default() {
  return null
}
export default function Default() {
  return null
}

モーダルの閉じ方

<Link href="/login"> などのクライアントナビゲーションを通じてモーダルが開始された場合、router.back() を呼び出すか Link コンポーネントを使用してモーダルを閉じることができます。

'use client'
import { useRouter } from 'next/navigation'
import { Modal } from 'components/modal'

export default async function Login() {
  const router = useRouter()
  return (
    <Modal>
      <span onClick={() => router.back()}>モーダルを閉じる</span>
      <h1>ログイン</h1>
      ...
    </Modal>
  )
}
'use client'
import { useRouter } from 'next/navigation'
import { Modal } from 'components/modal'

export default async function Login() {
  const router = useRouter()
  return (
    <Modal>
      <span onClick={() => router.back()}>モーダルを閉じる</span>
      <h1>ログイン</h1>
      ...
    </Modal>
  )
}

モーダルに関する詳細は インターセプティングルート セクションで説明されています。

他の場所にナビゲートしてモーダルを閉じたい場合は、キャッチオールルートも使用できます。

パラレルルーティングのキャッチオール図解
export default function CatchAll() {
  return null
}
export default function CatchAll() {
  return null
}

キャッチオールルートは default.js よりも優先されます。

条件付きルーティング

パラレルルーティングを使用して条件付きルーティングを実装できます。例えば、認証状態に応じて @dashboard または @login ルートをレンダリングできます。

import { getUser } from '@/lib/auth'

export default function Layout({
  dashboard,
  login,
}: {
  dashboard: React.ReactNode
  login: React.ReactNode
}) {
  const isLoggedIn = getUser()
  return isLoggedIn ? dashboard : login
}
import { getUser } from '@/lib/auth'

export default function Layout({ dashboard, login }) {
  const isLoggedIn = getUser()
  return isLoggedIn ? dashboard : login
}
パラレルルーティングの認証例