并行路由 (Parallel Routes)
并行路由 (Parallel Routing) 允许您在同一布局中同时或条件性地渲染多个页面。对于应用中高度动态的部分(如社交网站的控制面板和信息流),可以使用并行路由来实现复杂的路由模式。
例如,您可以同时渲染团队页面和分析页面。

并行路由允许您为每个独立加载的路由定义独立的错误和加载状态。

并行路由还允许您基于特定条件(如认证状态)条件性地渲染插槽。这使得同一 URL 下可以呈现完全分离的代码。

约定
并行路由通过命名的插槽 (slots) 创建。插槽使用 @folder
约定定义,并作为 props 传递给同层级的布局组件。
插槽 不是 路由段 (route segments),也 不会影响 URL 结构。文件路径
/@team/members
可通过/members
访问。
例如,以下文件结构定义了两个显式插槽:@analytics
和 @team
。

上述文件夹结构意味着 app/layout.js
中的组件现在接受 @analytics
和 @team
插槽 props,可以与 children
prop 并行渲染:
export default function Layout(props: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{props.children}
{props.team}
{props.analytics}
</>
)
}
export default function Layout(props) {
return (
<>
{props.children}
{props.team}
{props.analytics}
</>
)
}
须知:
children
prop 是一个隐式插槽,不需要映射到文件夹。这意味着app/page.js
等同于app/@children/page.js
。
未匹配路由
默认情况下,插槽内渲染的内容会匹配当前 URL。
当插槽未匹配时,Next.js 渲染的内容会根据路由技术和文件夹结构有所不同。
default.js
您可以定义 default.js
文件,当 Next.js 无法根据当前 URL 恢复插槽的活动状态时,将其作为备用渲染。
考虑以下文件夹结构。@team
插槽有 settings
目录,但 @analytics
没有。

导航
在导航时,Next.js 会渲染插槽之前的活动状态,即使它与当前 URL 不匹配。
刷新
刷新时,Next.js 会首先尝试渲染未匹配插槽的 default.js
文件。如果该文件不存在,则会渲染 404 页面。
未匹配路由的 404 页面有助于确保您不会意外渲染不应并行渲染的路由。
useSelectedLayoutSegment(s)
useSelectedLayoutSegment
和 useSelectedLayoutSegments
都接受 parallelRoutesKey
参数,允许您读取该插槽内的活动路由段。
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default async function Layout(props: {
//...
auth: React.ReactNode
}) {
const loginSegments = useSelectedLayoutSegment('auth')
// ...
}
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default async function Layout(props) {
const loginSegments = useSelectedLayoutSegment('auth')
// ...
}
当用户导航到 @auth/login
或 URL 栏中的 /login
时,loginSegments
将等于字符串 "login"
。
示例
模态框 (Modals)
并行路由可用于渲染模态框。

@auth
插槽渲染一个 <Modal>
组件,可以通过导航到匹配的路由(如 /login
)来显示。
export default async function Layout(props: {
// ...
auth: React.ReactNode
}) {
return (
<>
{/* ... */}
{props.auth}
</>
)
}
export default async function Layout(props) {
return (
<>
{/* ... */}
{props.auth}
</>
)
}
import { Modal } from 'components/modal'
export default function Login() {
return (
<Modal>
<h1>登录</h1>
{/* ... */}
</Modal>
)
}
import { Modal } from 'components/modal'
export default function Login() {
return (
<Modal>
<h1>登录</h1>
{/* ... */}
</Modal>
)
}
为确保模态框内容在不活动时不被渲染,您可以创建一个返回 null
的 default.js
文件。
export default function Default() {
return null
}
export default function Default() {
return null
}
关闭模态框
如果模态框是通过客户端导航(例如使用 <Link href="/login">
)启动的,您可以通过调用 router.back()
或使用 Link
组件来关闭模态框。
'use client'
import { useRouter } from 'next/navigation'
import { Modal } from 'components/modal'
export default async function Login() {
const router = useRouter()
return (
<Modal>
<span onClick={() => router.back()}>关闭模态框</span>
<h1>登录</h1>
...
</Modal>
)
}
'use client'
import { useRouter } from 'next/navigation'
import { Modal } from 'components/modal'
export default async function Login() {
const router = useRouter()
return (
<Modal>
<span onClick={() => router.back()}>关闭模态框</span>
<h1>登录</h1>
...
</Modal>
)
}
更多关于模态框的信息,请参阅 拦截路由 (Intercepting Routes) 部分。
如果您想导航到其他地方并关闭模态框,也可以使用全捕获 (catch-all) 路由。

export default function CatchAll() {
return null
}
export default function CatchAll() {
return null
}
全捕获路由的优先级高于
default.js
。
条件路由
并行路由可用于实现条件路由。例如,您可以根据认证状态渲染 @dashboard
或 @login
路由。
import { getUser } from '@/lib/auth'
export default function Layout({
dashboard,
login,
}: {
dashboard: React.ReactNode
login: React.ReactNode
}) {
const isLoggedIn = getUser()
return isLoggedIn ? dashboard : login
}
import { getUser } from '@/lib/auth'
export default function Layout({ dashboard, login }) {
const isLoggedIn = getUser()
return isLoggedIn ? dashboard : login
}
