Sponsor
ChatHubChatHub Use GPT-4, Gemini, Claude 3.5 and more chatbots side-by-side
ここをクリック
Menu

パラレルルート

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

例えば、ダッシュボードの場合、パラレルルートを使用してteamanalyticsのページを同時にレンダリングできます:

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と同等です。

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

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

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

補足:

  • 一致しないルートに対する404は、意図していないページにパラレルルートが誤ってレンダリングされないようにするために役立ちます。

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ファイルも作成する必要があります。

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内にlayoutファイルを作成して、2つのページ間でタブを共有します:

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で共有可能にする。
  • ページの更新時にモーダルを閉じるのではなく、コンテキストを保持する。
  • 前のルートに移動するのではなく、後方ナビゲーションでモーダルを閉じる
  • 前方ナビゲーションでモーダルを再開する

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

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

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

@authスロット内で、/(.)loginフォルダを更新して/loginルートをインターセプトします。<Modal>コンポーネントとその子要素を/(.)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が正しく更新されるようになります。

モーダルを開くには、@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]ページを持つ場合や、サイドモーダルでショッピングカートを開く場合などがあります。
  • インターセプトルートとパラレルルートを使用したモーダルのサンプルをご覧ください。

ローディングとエラーUI

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

Parallel routes enable custom error and loading states

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