Skip to content

平行路由

平行路由允许您在同一个布局中同时或条件性地渲染一个或多个页面。它们对于应用程序中的高动态部分非常有用,例如仪表板和社交网站上的动态。

例如,考虑一个仪表板,您可以使用平行路由同时渲染teamanalytics页面:

Parallel Routes Diagram

插槽

平行路由是使用命名的插槽创建的。插槽使用@folder约定来定义。例如,以下文件结构定义了两个插槽:@analytics@team

Parallel Routes File-system Structure

插槽作为属性传递给共享的父布局。对于上面的例子,app/layout.js中的组件现在接受@analytics@team插槽属性,并可以与children属性并行渲染:

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

然而,插槽不是路由段,并且不影响URL结构。例如,对于/@analytics/views,URL将是/views,因为@analytics是一个插槽。

须知

  • children属性是一个隐式插槽,不需要映射到文件夹。这意味着app/page.js等同于app/@children/page.js

Active state and navigation

活动状态和导航

默认情况下,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是一个隐式插槽,当Next.js无法恢复父页面的活动状态时,您还需要创建一个default.js文件,以呈现children的回退。

useSelectedLayoutSegment(s)

useSelectedLayoutSegmentuseSelectedLayoutSegments都接受一个parallelRoutesKey参数,允许您读取插槽内活动路由段。

tsx
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default function Layout({ auth }: { auth: React.ReactNode }) {
  const loginSegment = useSelectedLayoutSegment('auth')
  // ...
}
jsx
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

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

当用户导航到app/@auth/login(或URL栏中的/login)时,loginSegment将等于字符串"login"

示例

条件路由

您可以使用并行路由根据某些条件(例如用户角色)有条件地呈现路由。例如,为/admin/user角色呈现不同的仪表板页面:

Conditional routes diagram

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}</>
}
jsx
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文件,以便在两个页面之间共享标签:

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

tsx
import { Login } from '@/app/ui/login'

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

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

然后,在@auth插槽内,添加一个返回nulldefault.js文件。这确保了当对话框不活跃时不会渲染对话框。

tsx
export default function Default() {
  return null
}
jsx
export default function Default() {
  return null
}

在您的@auth插槽内,通过更新/(.)login文件夹来拦截/login路由。将<Modal>组件及其子组件导入到/(.)login/page.tsx文件中:

tsx
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}
jsx
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插槽作为属性传递给父布局,并与children属性一起渲染。

tsx
import Link from 'next/link'

export default function Layout({
  auth,
  children,
}: {
  auth: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <>
      <nav>
        <Link href="/login">打开模态框</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}
jsx
import Link from 'next/link'

export default function Layout({ auth, children }) {
  return (
    <>
      <nav>
        <Link href="/login">打开模态框</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}

当用户点击<Link>时,将打开模态框而不是导航到/login页面。然而,在刷新或初次加载时,导航到/login将带领用户前往主登录页面。

关闭模态框

您可以通过调用router.back()或使用Link组件来关闭模态框。

tsx
'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        关闭模态框
      </button>
      <div>{children}</div>
    </>
  )
}
jsx
'use client'

import { useRouter } from 'next/navigation'

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

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        关闭模态框
      </button>
      <div>{children}</div>
    </>
  )
}

当使用Link组件从不再需要渲染@auth插槽的页面导航离开时,我们使用一个通配符路由返回null

tsx
import Link from 'next/link'

export function Modal({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Link href="/">关闭模态框</Link>
      <div>{children}</div>
    </>
  )
}
jsx
import Link from 'next/link'

export function Modal({ children }) {
  return (
    <>
      <Link href="/">关闭模态框</Link>
      <div>{children}</div>
    </>
  )
}
tsx
export default function CatchAll() {
  return null
}
jsx
export default function CatchAll() {
  return null
}

须知:

  • 我们在@auth插槽中使用通配符路由来关闭模态框,因为活动状态和导航中描述的行为。由于客户端导航到不再匹配插槽的路由将仍然可见,我们需要将插槽匹配到返回null的路由以关闭模态框。
  • 其他示例可能包括在画廊中打开照片模态框,同时拥有专用的/photo/[id]页面,或在侧边模态框中打开购物车。
  • 查看示例,了解带有拦截和并行路由的模态框。

加载和错误界面

并行路由可以独立流式传输,允许您为每个路由定义独立的加载和错误状态:

并行路由启用自定义错误和加载状态

请参阅加载界面错误处理文档以获取更多信息。