跳至内容

并行路由

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

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

Parallel Routes Diagram

插槽

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

Parallel Routes File-system Structure

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

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道具是一个隐式插槽,不需要映射到文件夹。这意味着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 '...'
}

在您的@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插槽作为道具传递给父布局,并将其与children道具一起渲染。

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 '...'
}

或者,如果导航到任何其他页面(例如/foo/foo/bar等),您可以使用通配符插槽。

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

需要了解

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

加载和错误 UI

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

Parallel routes enable custom error and loading states

有关更多信息,请参阅加载 UI错误处理文档。