Menu

並列ルート

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

例えば、ダッシュボードを考える場合、並列ルートを使用して、teamページとanalyticsページを同時にレンダリングできます。

Parallel Routes Diagram

規約

スロット

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

Parallel Routes File-system Structure

スロットは、共有する親レイアウトにpropsとして渡されます。上記の例では、app/layout.js内のコンポーネントが@analytics@teamスロットpropsを受け入れ、children propと並行してレンダリングできます。

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

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

補足

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

default.js

初期ロード時またはフルページリロード時に、マッチしないスロットのフォールバックとしてレンダリングするdefault.jsファイルを定義できます。

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

Parallel Routes unmatched routes

/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パラメータを受け入れます。これにより、スロット内のアクティブなルートセグメントを読み取ることができます。

app/layout.tsx
TypeScript
'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ロール用に異なるページをレンダリングできます。

Conditional routes diagram
app/dashboard/layout.tsx
TypeScript
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つのサブページがあります。

Analytics slot with two subpages and a layout

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

app/@analytics/layout.tsx
TypeScript
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>
    </>
  )
}

モーダル

並列ルートは、ルートインターセプトと一緒に使用して、ディープリンクをサポートするモーダルを作成できます。これにより、モーダルを構築する際の一般的な課題を解決できます。例えば:

  • モーダルコンテンツをURLを通じて共有可能にする
  • ページがリフレッシュされても、モーダルを閉じるのではなく、コンテキストを保持する
  • 戻るナビゲーション時にモーダルを閉じる(前のルートに戻るのではなく)。
  • 前方ナビゲーション時にモーダルを再度開く

ユーザーがクライアント側ナビゲーションを使用してレイアウトからログインモーダルを開いたり、別の/loginページにアクセスしたりできるUI パターンを考えてください。

Parallel Routes Diagram

このパターンを実装するには、主なログインページをレンダリングする/loginルートを作成することから始めます。

Parallel Routes Diagram
app/login/page.tsx
TypeScript
import { Login } from '@/app/ui/login'
 
export default function Page() {
  return <Login />
}

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

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

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

app/@auth/(.)login/page.tsx
TypeScript
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
 
export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}

補足

モーダルを開く

これで、Next.jsルータを活用してモーダルを開いたり閉じたりできます。これにより、モーダルが開いている場合のURLと、戻る・進むナビゲーション時のURLが正確に更新されます。

モーダルを開くには、@authスロットを親レイアウトにpropとして渡し、children propと並行してレンダリングします。

app/layout.tsx
TypeScript
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
TypeScript
'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コンポーネントを使用して、@authスロットをレンダリングしないページからナビゲートする場合は、並列ルートがnullを返すコンポーネントにマッチすることを確認する必要があります。例えば、ルートページに戻る場合は、@auth/page.tsxコンポーネントを作成します。

app/ui/modal.tsx
TypeScript
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
TypeScript
export default function Page() {
  return null
}

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

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

補足

  • 並列ルートの動作方法のため、@authスロットでキャッチオールルートを使用します。スロットにマッチしなくなったルートへのクライアント側ナビゲーションは表示されたままになるため、モーダルを閉じるためにnullを返すルートにスロットをマッチさせる必要があります。
  • 他の例としては、ギャラリー内で写真モーダルを開きながら、同時に専用の/photo/[id]ページを持つ場合や、サイドモーダルでショッピングカートを開く場合などがあります。
  • インターセプトおよび並列ルートを使用したモーダルの例を確認してください。

Loading UIとError UI

並列ルートは独立してストリーミングできるため、各ルートに対して独立したエラーと読み込み状態を定義できます。

Parallel routes enable custom error and loading states

詳細は、Loading UIError Handlingドキュメントを参照してください。