パラレルルート (Parallel Routes)

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

例えば、ダッシュボードで teamanalytics ページを同時にレンダリングする場合:

パラレルルートの図解

規約

スロット

パラレルルートは名前付き スロット を使用して作成されます。スロットは @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}
    </>
  )
}

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

知っておくと便利:

  • children プロップスはフォルダにマッピングする必要のない暗黙的なスロットです。つまり、app/page.jsapp/@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) の使用

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

'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" と等しくなります。

条件付きルート

パラレルルートを使用して、ユーザーロールなどの条件に基づいてルートを条件付きでレンダリングできます。例えば、/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
}

タブグループ

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

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

レイアウトを持つ2つのサブページを含むアナリティクススロット

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

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

モーダル

パラレルルートはインターセプトルートと組み合わせて、ディープリンクをサポートするモーダルを作成できます。これにより、モーダル構築時の一般的な課題を解決できます:

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

クライアントサイドナビゲーションを使用してレイアウトからログインモーダルを開くか、別の /login ページにアクセスできるUIパターンを考えてみましょう:

パラレルルートの認証モーダル図解

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

パラレルルートモーダルログインページ
import { Login } from '@/app/ui/login'

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

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

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

知っておくと便利:

モーダルを開く

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

ユーザーが <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>
    </>
  )
}

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

または、他のページ(/foo/foo/bar など)にナビゲートする場合、キャッチオールスロットを使用できます:

export default function CatchAll() {
  return null
}

知っておくと便利:

  • モーダルを閉じるために @auth スロットでキャッチオールルートを使用するのは、パラレルルートの動作(#behavior)によるものです。スロットにマッチしなくなったルートへのクライアントサイドナビゲーションは表示されたままになるため、モーダルを閉じるには null を返すルートにマッチさせる必要があります。
  • 他の例としては、ギャラリーで写真モーダルを開きながら専用の /photo/[id] ページを持つことや、サイドモーダルでショッピングカートを開くことが含まれます。
  • インターセプトとパラレルルートを使用したモーダルの例を参照してください。

ローディングとエラーUI

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

パラレルルートによるカスタムエラーとローディング状態の有効化

詳細については、ローディングUIエラーハンドリング のドキュメントを参照してください。

On this page