Parallel Routes
Parallel Routesを使用すると、同じレイアウト内で1つ以上のページを同時または条件付きでレンダリングできます。これは、ソーシャルサイトのダッシュボードやフィードなど、アプリの高度に動的なセクションで役立ちます。
たとえば、ダッシュボードを考えると、team
ページとanalytics
ページを同時にレンダリングするためにparallel routesを使用できます:
Slots
Parallel routesは、名前付きslotsを使用して作成されます。slotsは@folder
規約で定義されます。たとえば、次のファイル構造では、@analytics
と@team
の2つのslotsが定義されています:
Slotsは共通の親レイアウトにpropsとして渡されます。上記の例では、app/layout.js
のコンポーネントは、現在@analytics
と@team
のslots propsを受け入れ、それらをchildren
propと一緒に並行してレンダリングできます:
- TypeScript
- JavaScript
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}
</>
)
}
ただし、slotsはルートセグメントではなく、URL構造には影響しません。たとえば、/@analytics/views
の場合、@analytics
はslotであるため、URLは/views
になります。Slotsは通常のページコンポーネントと組み合わせて、ルートセグメントに関連付けられた最終ページを形成します。このため、同じルートセグメントレベルで静的slotと動的slotを別々に持つことはできません。1つのslotが動的である場合、そのレベルのすべてのslotは動的でなければなりません。
Good to know:
children
propはマッピングの必要がない暗黙のslotです。つまり、app/page.js
はapp/@children/page.js
と同等です。
アクティブ状態とナビゲーション
デフォルトでは、Next.jsは各slotのアクティブな状態(またはサブページ)を追跡します。ただし、slot内にレンダリングされるコンテンツはナビゲーションの種類によります:
- ソフトナビゲーション:クライアントサイドナビゲーション中、Next.jsは部分的レンダリングを実行し、現在のURLと一致しなくても他のslotのアクティブなサブページを維持しながら、slot内のサブページを変更します。
- ハードナビゲーション:フルページのロード(ブラウザのリフレッシュ)後、Next.jsは現在のURLと一致しないslotのアクティブ状態を判断できません。その代わりに、一致しないslotに対して
default.js
ファイルをレンダリングするか、default.js
が存在しない場合は404
をレンダリングします。
Good to know:
- 一致しないルートの
404
は、意図しないページにparallel routeがレンダリングされないようにするのに役立ちます。
default.js
初期読み込みまたはフルページリロード時に一致しないslotに対してフォールバックとしてレンダリングするためのdefault.js
ファイルを定義できます。
次のフォルダ構造を考えます。@team
slotには/settings
ページがありますが、@analytics
にはありません。
/settings
にナビゲートすると、@team
slotは/settings
ページをレンダリングし、@analytics
slotの現在アクティブなページを維持します。
リフレッシュ時、Next.jsは@analytics
に対してdefault.js
をレンダリングします。default.js
が存在しない場合は、代わりに404
がレンダリングされます。
さらに、children
は暗黙のslotであるため、Next.jsが親ページのアクティブな状態を回復できない場合に備えて、children
用にフォールバックをレンダリングするdefault.js
ファイルを作成する必要があります。
useSelectedLayoutSegment(s)
useSelectedLayoutSegment
とuseSelectedLayoutSegments
は両方ともparallelRoutesKey
パラメータを受け入れ、slot内のアクティブなルートセグメントを読み取ることができます。
- TypeScript
- JavaScript
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
ユーザーがapp/@auth/login
(またはURLバーの/login
)にナビゲートすると、loginSegment
は文字列"login"
と等しくなります。
例
条件付きルート
Parallel Routesを使用して、ユーザーの役割などの特定の条件に基づいてルートを条件付きでレンダリングすることができます。たとえば、/admin
または/user
役割の異なるダッシュボードページを表示するために:
- TypeScript
- JavaScript
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
}
タブグループ
ユーザーがslotを独立してナビゲートできるようにするために、slot内にlayout
を追加できます。これはタブを作成するのに便利です。
たとえば、@analytics
slotには/page-views
と/visitors
の2つのサブページがあります。
@analytics
内で、タブを2つのページ間で共有するためにlayout
ファイルを作成します:
- TypeScript
- JavaScript
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>
</>
)
}
import Link from 'next/link'
export default function Layout({ children }) {
return (
<>
<nav>
<Link href="/page-views">Page Views</Link>
<Link href="/visitors">Visitors</Link>
</nav>
<div>{children}</div>
</>
)
}
モーダル
Parallel Routesは、Intercepting Routesと組み合わせることで、ディープリンクをサポートするモーダルを作成できます。これにより、モーダルを構築する際の一般的な課題を解決できます:
- モーダルのコンテンツをURLを通じて共有可能にする
- ページをリフレッシュしたときにモーダルを閉じるのではなく、コンテキストを保持する
- 戻るナビゲーションでモーダルを閉じるのではなく、前のルートに戻る
- 進むナビゲーションでモーダルを再度開く
以下のUIパターンを検討します。ここでは、ユーザーがクライアントサイドナビゲーションを使用してレイアウトからログインモーダルを開くか、別の/login
ページにアクセスできます:
このパターンを実装するには、まずメインのログインページをレンダリングする/login
ルートを作成します。
- TypeScript
- JavaScript
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
slot内にdefault.js
ファイルを追加し、null
を返します。これにより、モーダルがアクティブでないときにレンダリングされないようにします。
- TypeScript
- JavaScript
export default function Default() {
return null
}
export default function Default() {
return null
}
@auth
slot内で、/(.)login
フォルダを更新して/login
ルートをインターセプトします。<(Modal)>コンポーネント
とその子要素を/(.)login/page.tsx
ファイルにインポートします:
- TypeScript
- JavaScript
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:
- ルートをインターセプトするために使用される規約、例えば
(.)
は、ファイルシステム構造に依存します。詳しくはIntercepting Routesの規約を参照してください。- モーダルコンテンツ(
<(Login)>
)とは別に<(Modal)>
機能を分離することで、モーダル内のコンテンツ、例えばフォームなどをサーバーコンポーネントにすることができます。詳しくはクライアントコンポーネントとサーバーコンポーネントのインターリーブを参照してください。
モーダルを開く
これで、Next.js routerを利用してモーダルを開閉できます。これにより、モーダルが開いているときや、前後にナビゲートするときにURLが正しく更新されます。
モーダルを開くには、親レイアウトに@auth
slotをpropとして渡し、children
propと一緒にレンダリングします。
- TypeScript
- JavaScript
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
ページに遷移する代わりにモーダルが開きます。ただし、リフレッシュ時や初期読み込み時には、/login
に遷移するとメインのログインページが表示されます。
モーダルを閉じる
router.back()
を呼び出すか、Link
コンポーネントを使用してモーダルを閉じることができます。
- TypeScript
- JavaScript
'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
コンポーネントを使用する場合、parallel routeがnull
を返すコンポーネントに一致することを確認する必要があります。たとえば、ルートページに戻るときには、@auth/page.tsx
コンポーネントを作成します:
- TypeScript
- JavaScript
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>
</>
)
}
- TypeScript
- JavaScript
export default function Page() {
return null
}
export default function Page() {
return null
}
または、他のページ(/foo
、/foo/bar
など)にナビゲートする場合、catch-all slotを使用できます:
- TypeScript
- JavaScript
export default function CatchAll() {
return null
}
export default function CatchAll() {
return null
}
Good to know:
- モーダルを閉じるために
@auth
slotでcatch-allルートを使用するのは、アクティブ状態とナビゲーションで説明した動作のためです。slotと一致しないルートへのクライアントサイドナビゲーションは表示されたままになるため、モーダルを閉じるためには、slotをnull
を返すルートに一致させる必要があります。- 他の例としては、ギャラリーで写真モーダルを開きつつ、専用の
/photo/[id]
ページを持つことや、サイドモーダルでショッピングカートを開くことが含まれます。- Intercepted RoutesおよびParallel Routesを使用したモーダルの例を見る
ローディングとエラーUI
Parallel Routesは独立してストリームされるため、各ルートに対して独立したエラーとローディング状態を定義できます: