Lang x Lang

Parallel Routes

Parallel Routes は、同時にまたは条件付きで、同じ layout 内の 1 つ以上のページを render することを許可します。これらは、dashboard やソーシャルサイトのフィードなど、app の非常に dynamic なセクションで役立ちます。

たとえば、dashboard を考慮すると、parallel routes を使用して、team および analytics ページを同時に render できます。

Parallel Routes Diagram

Slots

Parallel routes は名前付きのスロットを使って作られます。スロットは@folderの慣例で定義されます。たとえば、以下のファイル構造では二つのスロット:@analytics@teamが定義されています:

Parallel Routes File-system Structure

スロットは、共有親の layout に props として渡されます。上記の例では、app/layout.js内の component は、@analytics@teamのスロット props を受け入れ、それらを parallel でchildren prop と並行して render することができます。

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

しかし、スロットは route segmentsはなく、URL 構造に影響を及ぼしません。例えば、/dashboard/@analytics/viewsの場合、@analytics がスロットであるため、URL は /dashboard/views となります。

Good to know:

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

Active state and navigation

default として、Next.js は各スロットのアクティブな状態(またはサブページ)を追跡します。ただし、スロット内にレンダリングされる内容は type のナビゲーションに依存します:

  • ソフトナビゲーション: クライアントサイドナビゲーション中、 Next.js は部分的な render を行い、スロット内のサブページを変更しながら、他のスロットのアクティブなサブページを保持します、たとえそれらが現在の URL と一致しなくても。
  • 難解なナビゲーション: フルページのロード(ブラウザの refresh )後、 Next.js は現在の URL に一致しないスロットのアクティブ状態を決定できません。代わりに、一致しないスロットには default.js ファイルが render されます。もし default.js が存在しない場合は、404 が出力されます。

Good to know:

  • 404は、マッチしない routes に対して、間違えてページ上で parallel route を render しないように保証する助けとなります。

default.js

default.jsファイルを定義して、初回の読み込みやフルページの再読み込み時にマッチしないスロットの fallback として render することができます。

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

Parallel Routes unmatched routes

/dashboard/settings に移動すると、@team スロットは、@analytics スロットの現在アクティブなページを維持しながら /settings ページを render します。

refresh により、 Next.js は @analytics のために default.js を render します。もし default.js が存在しない場合、代わりに 404 がレンダリングされます。

さらに、childrenは暗黙的なスロットなので、default.jsファイルを作成して、Next.js が親ページのアクティブな状態を回復できない場合のchildrenのための fallback を render する必要があります。

useSelectedLayoutSegment(s)

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

app/layout.tsx
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

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

import { useSelectedLayoutSegment } from 'next/navigation'

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

ユーザーが app/@auth/login (または URL バーの /login)に移動すると、 loginSegments は string の "login" と等しくなります。

Examples

条件付き Routes

ユーザーロールなどの特定の条件に基づいて条件付きで render routes を行うために、Parallel Routes を使用することができます。たとえば、/admin または /user ロールに対して、異なる dashboard ページを render する場合は次のようになります:

Conditional 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}</>
}
app/dashboard/layout.js
import { checkUserRole } from '@/lib/auth'

export default function Layout({ user, admin }) {
  const role = checkUserRole()
  return <>{role === 'admin' ? admin : user}</>
}

タブグループ

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

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

Analytics slot with two subpages and a layout

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

app/dashboard/@analytics/layout.tsx
import Link from 'next/link'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Link href="/dashboard/page-views">Page Views</Link>
        <Link href="/dashboard/visitors">Visitors</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}
app/dashboard/@analytics/layout.js
import Link from 'next/link'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Link href="/dashboard/page-views">Page Views</Link>
        <Link href="/dashboard/visitors">Visitors</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}

Modals

Parallel Routes は Intercepting Routes と一緒に使用して、モーダルを作成することができます。これにより、モーダルを作成する際によくある課題を解決することができます。これらの課題には次のようなものがあります:

  • モーダルコンテントを URL を通じて共有可能にする
  • ページがリフレッシュされたときに、モーダルを閉じる代わりに、context を保持する
  • バックワードナビゲーションでモーダルを閉じる これは、前の route に戻るのではなく。
  • 進行方向のナビゲーションでモーダルを再開する

次の UI パターンを考えてみてください。ここでは、ユーザーが Client サイドのナビゲーションを使用して layout からログインモーダルを開くか、別の/loginページにアクセスすることができます:

Parallel Routes Diagram

このパターンを実装するために、まずメインのログインページをレンダリングする/loginの route を作成することから start してください。

Parallel Routes Diagram
app/login/page.tsx
import { Login } from '@/app/ui/login'

export default function Page() {
  return <Login />
}
app/login/page.js
import { Login } from '@/app/ui/login'

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

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

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

あなたの@authスロットの中で、/(.)loginフォルダを更新して/loginの route をインターセプトします。 <Modal>の component とその children を/(.)login/page.tsxファイルに Import します。

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>
  )
}
app/@auth/(.)login/page.js
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スロットを親の layout と render にプロップとして渡し、それをchildrenプロップと一緒にレンダリングします。

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>
    </>
  )
}
app/layout.js
import Link from 'next/link'

export default function Layout({ auth, children }) {
  return (
    <>
      <nav>
        <Link href="/login">Open modal</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}

ユーザーが<Link>をクリックすると、/loginページに移動する代わりにモーダルが開きます。しかし、refresh または初期ロード時に/loginに遷移すると、ユーザーはメインのログインページに移動します。

モーダルを閉じる

router.back()を呼び出すか、Link component を使用することで、モーダルを閉じることができます。

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>
    </>
  )
}
app/ui/modal.js
'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }) {
  const router = useRouter()

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        Close modal
      </button>
      <div>{children}</div>
    </>
  )
}

Link component を使用して @auth スロットをもう render しないページから離れる場合、null を返す catch-all route を使用します。

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/ui/modal.js
import Link from 'next/link'

export function Modal({ children }) {
  return (
    <>
      <Link href="/">Close modal</Link>
      <div>{children}</div>
    </>
  )
}
app/@auth/[...catchAll]/page.tsx
export default function CatchAll() {
  return null
}
app/@auth/[...catchAll]/page.js
export default function CatchAll() {
  return null
}

Good to know:

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

Loading と Error UI

Parallel Routes は独立して Streaming できます。これにより、それぞれの route に対して独立した error と loading の状態を定義することができます:

Parallel routes enable custom error and loading states

詳細については、Loading UIError Handling のドキュメンテーションをご覧ください。

当社サイトでは、Cookie を使用しています。各規約をご確認の上ご利用ください:
Cookie Policy, Privacy Policy および Terms of Use