Parallel Routes
Parallel Routes は、同時にまたは条件付きで、同じ layout 内の 1 つ以上のページを render することを許可します。これらは、dashboard やソーシャルサイトのフィードなど、app の非常に dynamic なセクションで役立ちます。
たとえば、dashboard を考慮すると、parallel routes を使用して、team
および analytics
ページを同時に render できます。
Slots
Parallel routes は名前付きのスロットを使って作られます。スロットは@folder
の慣例で定義されます。たとえば、以下のファイル構造では二つのスロット:@analytics
と @team
が定義されています:
スロットは、共有親の layout に props として渡されます。上記の例では、app/layout.js
内の component は、@analytics
と@team
のスロット props を受け入れ、それらを parallel でchildren
prop と並行して render することができます。
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
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
にはありません。
/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)
useSelectedLayoutSegment
と useSelectedLayoutSegments
の両方は parallelRoutesKey
パラメータを受け入れ、これによりスロット内のアクティブな route セグメントを読み取ることができます。
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegments = useSelectedLayoutSegment('auth')
// ...
}
'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 する場合は次のようになります:
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}</>
}
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
内で、二つのページ間でタブを共有するための layout
ファイルを作成します:
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>
</>
)
}
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
ページにアクセスすることができます:
このパターンを実装するために、まずメインのログインページをレンダリングする/login
の route を作成することから start してください。
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
次に、@auth
スロットの中に、null
を返す default.js
ファイルを追加します。これにより、モーダルがアクティブでないときにはレンダリングされないことが保証されます。
export default function Default() {
return null
}
export default function Default() {
return null
}
あなたの@auth
スロットの中で、/(.)login
フォルダを更新して/login
の route をインターセプトします。 <Modal>
の component とその children を/(.)login/page.tsx
ファイルに Import します。
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
Good to know:
- route をインターセプトするために使用される慣習は、例えば
(.)
など、あなたのファイルシステムの構造に依存します。Routes をインターセプトするための慣習をご覧ください。<Modal>
の機能をモーダルコンテンツ (<Login>
)から分離することで、モーダル内の任意のコンテンツ、例えば フォームが、Server Components であることを保証できます。詳細は Client と Server Components の交互処理 をご覧ください。
モーダルを開く
これで、Next.js router を利用してモーダルを開閉できます。これにより、モーダルが開いているとき、そして前後に移動するときに URL が正しく更新されることが保証されます。
モーダルを開くには、@auth
スロットを親の layout と render にプロップとして渡し、それをchildren
プロップと一緒にレンダリングします。
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>
</>
)
}
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 を使用することで、モーダルを閉じることができます。
'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>
</>
)
}
'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 を使用します。
import Link from 'next/link'
export function Modal({ children }: { children: React.ReactNode }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export function Modal({ children }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
export default function CatchAll() {
return null
}
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 の状態を定義することができます:
詳細については、Loading UI と Error Handling のドキュメンテーションをご覧ください。