并行路由
并行路由允许您在同一布局中同时或条件性渲染多个页面。这种模式特别适用于应用中高度动态的部分,例如社交网站的仪表盘和信息流。
举例来说,在一个仪表盘应用中,您可以使用并行路由同时渲染 team
和 analytics
两个页面:

约定
插槽
并行路由通过命名的插槽实现。插槽使用 @folder
约定定义。例如,以下文件结构定义了两个插槽:@analytics
和 @team
:

插槽会作为属性传递给共享的父布局组件。对于上面的例子,app/layout.js
中的组件现在可以接收 @analytics
和 @team
插槽属性,并与 children
属性并行渲染:
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
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
。
default.js
您可以定义 default.js
文件,用于在初始加载或整页刷新时为不匹配的插槽提供回退渲染。
考虑以下文件夹结构。@team
插槽有 /settings
页面,但 @analytics
没有。

当导航到 /settings
时,@team
插槽会渲染 /settings
页面,同时保持 @analytics
插槽当前的活动页面。
刷新页面时,Next.js 会为 @analytics
渲染 default.js
。如果 default.js
不存在,则会渲染 404
。
此外,由于 children
是隐式插槽,您也需要创建 default.js
文件,以便在 Next.js 无法恢复父页面活动状态时为 children
提供回退渲染。
行为
默认情况下,Next.js 会跟踪每个插槽的活动状态(或子页面)。但插槽内渲染的内容取决于导航类型:
- 软导航:在客户端导航期间,Next.js 会执行部分渲染,更改插槽内的子页面,同时保持其他插槽的活动子页面,即使它们与当前 URL 不匹配。
- 硬导航:在整页加载(浏览器刷新)后,Next.js 无法确定与当前 URL 不匹配的插槽的活动状态。相反,它会为不匹配的插槽渲染
default.js
文件,如果default.js
不存在则渲染404
。
须知:
- 对于不匹配路由的
404
有助于确保您不会意外地在不适合的页面上渲染并行路由。
示例
使用 useSelectedLayoutSegment(s)
useSelectedLayoutSegment
和 useSelectedLayoutSegments
都接受 parallelRoutesKey
参数,允许您读取插槽内的活动路由段。
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
'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
角色渲染不同的仪表盘页面:

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
}
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
内,创建一个 layout
文件以在两个页面之间共享标签:
import Link from 'next/link'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Link href="/page-views">页面浏览</Link>
<Link href="/visitors">访客</Link>
</nav>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export default function Layout({ children }) {
return (
<>
<nav>
<Link href="/page-views">页面浏览</Link>
<Link href="/visitors">访客</Link>
</nav>
<div>{children}</div>
</>
)
}
模态框
并行路由可以与拦截路由结合使用,创建支持深度链接的模态框。这有助于解决构建模态框时的常见挑战:
- 通过 URL 共享模态框内容。
- 刷新页面时保留上下文,而不是关闭模态框。
- 在后退导航时关闭模态框,而不是返回上一个路由。
- 在前进导航时重新打开模态框。
考虑以下 UI 模式,用户可以通过客户端导航从布局中打开登录模态框,或访问单独的 /login
页面:

要实现此模式,首先创建一个渲染主登录页面的 /login
路由。

import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
然后,在 @auth
插槽内添加 default.js
文件,返回 null
。这确保模态框在不活动时不会渲染。
export default function Default() {
return null
}
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>
)
}
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
属性一起渲染。
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>
</>
)
}
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
组件来关闭模态框。
'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>
</>
)
}
'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
的组件。例如,在导航回根页面时,我们创建一个 @auth/page.tsx
组件:
import Link from 'next/link'
export function Modal({ children }: { children: React.ReactNode }) {
return (
<>
<Link href="/">关闭模态框</Link>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export function Modal({ children }) {
return (
<>
<Link href="/">关闭模态框</Link>
<div>{children}</div>
</>
)
}
export default function Page() {
return null
}
export default function Page() {
return null
}
或者如果要导航到其他页面(如 /foo
、/foo/bar
等),可以使用通配插槽:
export default function CatchAll() {
return null
}
export default function CatchAll() {
return null
}
须知:
- 我们在
@auth
插槽中使用通配路由来关闭模态框,这是因为并行路由的行为(#behavior)。由于客户端导航到不再匹配插槽的路由时,插槽仍会保持可见,我们需要将插槽匹配到一个返回null
的路由来关闭模态框。- 其他示例可能包括在相册中打开照片模态框,同时拥有专用的
/photo/[id]
页面,或在侧边栏中打开购物车。- 查看示例了解如何使用拦截和并行路由实现模态框。
加载和错误 UI
并行路由可以独立流式传输,允许您为每个路由定义独立的错误和加载状态:
