パラレルルート
パラレルルートを使用すると、同じレイアウト内で1つまたは複数のページを同時にまたは条件付きでレンダリングできます。これはダッシュボードやソーシャルサイトのフィードなど、高度に動的なアプリケーションのセクションに役立ちます。
例えば、ダッシュボードの場合、パラレルルートを使用してteam
とanalytics
のページを同時にレンダリングできます:
スロット
パラレルルートは名前付きスロットを使用して作成されます。スロットは@folder
という規則で定義されます。例えば、次のファイル構造は@analytics
と@team
という2つのスロットを定義しています:
スロットは共有の親レイアウトに props として渡されます。上記の例では、app/layout.js
のコンポーネントは@analytics
と@team
のスロット props を受け取り、children
prop と並行してレンダリングできます:
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.js
はapp/@children/page.js
と同等です。
アクティブな状態とナビゲーション
デフォルトでは、Next.jsは各スロットのアクティブな_状態_(またはサブページ)を追跡します。ただし、スロット内にレンダリングされるコンテンツはナビゲーションのタイプによって異なります:
- ソフトナビゲーション:クライアントサイドナビゲーション中、Next.jsは部分的なレンダリングを実行し、スロット内のサブページを変更しますが、現在のURLに一致しなくても、他のスロットのアクティブなサブページは維持されます。
- ハードナビゲーション:フルページロード(ブラウザの更新)後、Next.jsは現在のURLに一致しないスロットのアクティブな状態を判断できません。代わりに、一致しないスロットに対して
default.js
ファイルをレンダリングするか、default.js
が存在しない場合は404
をレンダリングします。
補足:
- 一致しないルートに対する
404
は、意図していないページにパラレルルートが誤ってレンダリングされないようにするために役立ちます。
default.js
初期ロードまたはフルページリロード中に、一致しないスロットのフォールバックとしてレンダリングするdefault.js
ファイルを定義できます。
次のフォルダ構造を考えてみましょう。@team
スロットには/settings
ページがありますが、@analytics
にはありません。
/settings
に移動すると、@team
スロットは/settings
ページをレンダリングしながら、@analytics
スロットの現在アクティブなページを維持します。
更新時、Next.jsは@analytics
に対してdefault.js
をレンダリングします。default.js
が存在しない場合は、代わりに404
がレンダリングされます。
さらに、children
は暗黙的なスロットであるため、Next.jsが親ページのアクティブな状態を回復できない場合にchildren
のフォールバックをレンダリングするためのdefault.js
ファイルも作成する必要があります。
useSelectedLayoutSegment(s)
useSelectedLayoutSegment
とuseSelectedLayoutSegments
はどちらもparallelRoutesKey
パラメータを受け入れ、スロット内のアクティブなルートセグメントを読み取ることができます。
'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
ロールに対して異なるダッシュボードページをレンダリングするには:
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
内にlayout
ファイルを作成して、2つのページ間でタブを共有します:
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
ページにアクセスできます:
このパターンを実装するには、まずメインのログインページをレンダリングする/login
ルートを作成します。
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
次に、@auth
スロット内に、null
を返すdefault.js
ファイルを追加します。これにより、モーダルがアクティブでない場合にレンダリングされないようになります。
export default function Default() {
return null
}
@auth
スロット内で、/(.)login
フォルダを更新して/login
ルートをインターセプトします。<Modal>
コンポーネントとその子要素を/(.)login/page.tsx
ファイルにインポートします:
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
補足:
- ルートをインターセプトするために使用される規則(例:
(.)
)は、ファイルシステム構造によって異なります。インターセプトルートの規則を参照してください。<Modal>
機能をモーダルコンテンツ(<Login>
)から分離することで、モーダル内のコンテンツ(例:フォーム)がサーバーコンポーネントであることを確認できます。詳細については、クライアントコンポーネントとサーバーコンポーネントの組み合わせを参照してください。
モーダルの開き方
次に、Next.jsルーターを活用してモーダルの開閉を行います。これにより、モーダルが開いているとき、および前後にナビゲートするときに、URLが正しく更新されるようになります。
モーダルを開くには、@auth
スロットを親レイアウトにpropとして渡し、children
propと一緒にレンダリングします。
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
コンポーネントを使用してモーダルを閉じることができます。
'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
コンポーネントを作成します:
import Link from 'next/link'
export function Modal({ children }: { children: React.ReactNode }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
export default function Page() {
return null
}
または、他のページ(/foo
、/foo/bar
など)にナビゲートする場合は、キャッチオールスロットを使用できます:
export default function CatchAll() {
return null
}
補足:
- アクティブな状態とナビゲーションで説明した動作により、
@auth
スロットでキャッチオールルートを使用してモーダルを閉じています。スロットに一致しないルートへのクライアントサイドナビゲーションでは、そのスロットが引き続き表示されるため、モーダルを閉じるにはnull
を返すルートにスロットを一致させる必要があります。- 他の例としては、ギャラリーで写真モーダルを開きながら専用の
/photo/[id]
ページを持つ場合や、サイドモーダルでショッピングカートを開く場合などがあります。- インターセプトルートとパラレルルートを使用したモーダルのサンプルをご覧ください。
ローディングとエラーUI
パラレルルートは独立してストリーミングできるため、各ルートに独立したエラーとローディング状態を定義できます: