跳至內容

平行路由

平行路由允許您在同一個版面配置中同時或有條件地渲染一個或多個頁面。它們對於應用程式中高度動態的部分非常有用,例如儀表板和社群網站上的動態消息。

例如,考慮到儀表板,您可以使用平行路由來同時渲染 teamanalytics 頁面

Parallel Routes Diagram

插槽

平行路由是使用具名的插槽建立的。插槽是使用 @資料夾 慣例定義的。例如,以下檔案結構定義了兩個插槽:@analytics@team

Parallel Routes File-system Structure

插槽作為 props 傳遞給共用的父版面配置。對於上面的範例,app/layout.js 中的元件現在接受 @analytics@team 插槽 props,並且可以與 children prop 平行渲染它們

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

然而,插槽不是路由區段,並且不會影響 URL 結構。例如,對於 /@analytics/views,URL 將會是 /views,因為 @analytics 是一個插槽。插槽與常規的頁面元件結合,形成與路由區段關聯的最終頁面。因此,您不能在同一個路由區段層級同時擁有獨立的靜態動態插槽。如果一個插槽是動態的,則該層級的所有插槽都必須是動態的。

要知道:

  • 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 沒有。

Parallel Routes unmatched routes

導航至 /settings 時,@team 插槽將渲染 /settings 頁面,同時維護 @analytics 插槽目前活動的頁面。

在重新整理時,Next.js 將為 @analytics 渲染 default.js。如果 default.js 不存在,則會改為渲染 404

此外,由於 children 是一個隱含的插槽,您也需要建立一個 default.js 檔案,以便在 Next.js 無法恢復父頁面的活動狀態時,為 children 渲染後備方案。

useSelectedLayoutSegment(s)

useSelectedLayoutSegmentuseSelectedLayoutSegments 都接受 parallelRoutesKey 參數,讓您可以讀取插槽內的活動路由區段。

app/layout.tsx
'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
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

Analytics slot with two subpages and a layout

@analytics 內,建立一個 layout 檔案,以在兩個頁面之間共用分頁

app/@analytics/layout.tsx
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
import { Login } from '@/app/ui/login'
 
export default function Page() {
  return <Login />
}

然後,在 @auth 插槽內,新增一個 default.js 檔案,該檔案會傳回 null。這可確保在彈出視窗未處於活動狀態時不會渲染。

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

在您的 @auth 插槽內,透過更新 /(.)login 資料夾來攔截 /login 路由。將 <Modal> 元件及其子元件匯入到 /(.)login/page.tsx 檔案中

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>
  )
}

要知道

  • 用於攔截路由的慣例(例如 (.))取決於您的檔案系統結構。請參閱攔截路由慣例
  • 透過將 <Modal> 功能與彈出視窗內容 (<Login>) 分開,您可以確保彈出視窗內的任何內容(例如 表單)都是伺服器元件。請參閱交錯使用客戶端和伺服器元件以取得更多資訊。

開啟彈出視窗

現在,您可以利用 Next.js 路由器來開啟和關閉彈出視窗。這可確保在彈出視窗開啟時以及在向後和向前導航時,URL 會正確更新。

若要開啟彈出視窗,請將 @auth 插槽作為 prop 傳遞給父版面配置,並與 children prop 一起渲染。

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>
    </>
  )
}

當使用者點擊 <Link> 時,將會開啟彈出視窗,而不是導航至 /login 頁面。但是,在重新整理或初始載入時,導航至 /login 將會將使用者帶到主要登入頁面。

關閉彈出視窗

您可以透過呼叫 router.back() 或使用 Link 元件來關閉彈出視窗。

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>
    </>
  )
}

當使用 Link 元件導航離開不應再渲染 @auth 插槽的頁面時,我們需要確保平行路由符合傳回 null 的元件。例如,當導航回到根頁面時,我們建立一個 @auth/page.tsx 元件

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/@auth/page.tsx
export default function Page() {
  return null
}

或者,如果導航至任何其他頁面(例如 /foo/foo/bar 等),您可以使用 catch-all 插槽

app/@auth/[...catchAll]/page.tsx
export default function CatchAll() {
  return null
}

要知道

  • 我們在 @auth 插槽中使用 catch-all 路由來關閉彈出視窗,這是因為 活動狀態與導航 中描述的行為。由於客戶端導航到不再符合插槽的路由仍會保持可見,因此我們需要將插槽與傳回 null 的路由相符,才能關閉彈出視窗。
  • 其他範例可能包括在圖庫中開啟照片彈出視窗,同時也擁有專用的 /photo/[id] 頁面,或在側邊彈出視窗中開啟購物車。
  • 檢視具有攔截路由和平行路由的彈出視窗範例

載入與錯誤 UI

平行路由可以獨立串流,讓您可以為每個路由定義獨立的錯誤和載入狀態

Parallel routes enable custom error and loading states

請參閱載入 UI錯誤處理 文件以取得更多資訊。