并行路由

并行路由允许你同时或者有条件地在同一 layout 下渲染一个或者多个 page。

对于应用程序中高度动态的部分,比如仪表板和社交网站上的信息,可以使用并行路由来实现复杂的路由模式。

举个例子,你可以同时渲染team和analytics页面

Parallel Routes Diagram

并行路由允许你为每个路由定义独立的 error 和 loading 状态。因为它们是独立被 stream 进来的。

Parallel routes enable custom error and loading states

并行路由也允许你根据某些条件(例如身份验证)渲染一个 slot(插槽),从而在相同的 url 上实现完全分离的代码。

Conditional routes diagram

约定

并行路由是使用命名 slots(插槽)实现的。slots 使用@folder 约定定义,并作为属性传递给同级别的布局。

slots 不会影响路由的 segments 以及不会影响 URL 的结构。路径/@team/members可以在/members访问。

举个例子,下面的文件结构定义了两个插槽: @analytics 和 @team.

Parallel Routes File-system Structure

上面的文件结构意味着在app/lauout.js的组件会接受到 @analytics 和 @team插槽的 props,并且能够将它们与children并行。

js
1export default function Layout(props: {
2  children: React.ReactNode
3  analytics: React.ReactNode
4  team: React.ReactNode
5}) {
6  return (
7    <>
8      {props.children}
9      {props.team}
10      {props.analytics}
11    </>
12  )
13}

children props 是一个隐式 slot,不需要映射到文件夹。这意味着app/page.js相当于app/@children/page.js。

不匹配的路由

默认情况下,slot 中呈现的内容会匹配当前 url。

在 slot 不匹配的情况下,Next.js 呈现的内容因路由技术和文件夹结构而异。

default.js

当 Next.js 无法根据当前 URL 恢复slot的活动状态时,您可以定义一个 default.js 文件作为后备渲染。

参考以下文件夹结构。@team 插槽有一个 settings 目录,但 @analytics 没有。

Parallel Routes unmatched routes

在导航时,Next.js 将呈现插槽以前的活动状态,即使它与当前 URL 不匹配。

reload

重新加载时,Next.js 将首先尝试渲染不匹配插槽的 default.js 文件。如果不可用,则会渲染 404。

未匹配路由的 404 页面有助于确保您不会意外地渲染不应该并行呈现的路由。

useSelectedLayoutSegment(s)

useSelectedLayoutSegment 和 useSelectedLayoutSegments 两个都接受一个参数 parallelRoutesKey, 允许你在该插槽中读取活跃的路由。

typescript
1'use client'
2
3import { useSelectedLayoutSegment } from 'next/navigation'
4
5export default async function Layout(props: {
6  //...
7  auth: React.ReactNode
8}) {
9  const loginSegments = useSelectedLayoutSegment('auth')
10  // ...
11}

当用户导航到 @auth/login,或在 URL 地址栏中输入 /login 时,loginSegments 将等于字符串 login。

例子

Modals(模态框)

并行路由可以用来渲染模态框

Parallel Routes Diagram

@auth 插槽可以渲染<Modal>组件,可以通过导航到匹配的路由(例如 /login)来显示。

typescript
1// app/layout.tsx
2export default async function Layout(props: {
3  // ...
4  auth: React.ReactNode
5}) {
6  return (
7    <>
8      {/* ... */}
9      {props.auth}
10    </>
11  )
12}
typescript
1// app/@auth/login/page.tsx
2import { Modal } from 'components/modal'
3
4export default function Login() {
5  return (
6    <Modal>
7      <h1>Login</h1>
8      {/* ... */}
9    </Modal>
10  )
11}

为了确保模态的内容在它不活动时不会被渲染,可以创建一个返回 null 的 default.js 文件。

typescript
1export default function Default() {
2  return null
3}

关闭模态框

如果模态框是通过客户端导航初始化的,例如通过使用 <Link href="/login">,您可以通过调用 router.back() 或使用 Link 组件来关闭模态框。

typescript
1// app/@auth/login/page.tsx
2'use client'
3import { useRouter } from 'next/navigation'
4import { Modal } from 'components/modal'
5
6export default async function Login() {
7  const router = useRouter()
8  return (
9    <Modal>
10      <span onClick={() => router.back()}>Close modal</span>
11      <h1>Login</h1>
12      ...
13    </Modal>
14  )
15}

如果您想要导航到其他位置并关闭模态框,还可以使用一个捕获所有路由。

Parallel Routes Diagram

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

catchAll路由优先于 default.js。

条件路由

并行路由可以用于实现条件性路由。例如,您可以根据身份验证状态呈现 @dashboard 或 @login 路由。

typescript
1import { getUser } from '@/lib/auth'
2
3export default function Layout({ dashboard, login }: { dashboard: React.ReactNode; login: React.ReactNode }) {
4  const isLoggedIn = getUser()
5  return isLoggedIn ? dashboard : login
6}

Parallel routes authentication example