所有规则均为 13.4+ 正式版标准,无冗余内容,完全覆盖工业级 T3 项目开发需求。
一、基础认知
1. 是什么
并行路由是让同一个 URL 下可以同时渲染多个完全独立的路由模块的能力,每个模块有自己的路由匹配、加载、错误边界,互不影响。
典型 T3 项目使用场景:后台的侧边栏、主内容、全局模态框、通知栏四个模块完全解耦,各自走自己的路由逻辑。
2. 前置要求
- 槽位命名:所有并行路由的根文件夹必须以
@开头,比如@sidebar、@modal,不会生成独立的可访问 URL,仅作为槽位标识 - 根 Layout 传参:根
app/layout.tsx必须接收和@后同名的 props,和主内容children平级渲染
// app/layout.tsx
export default function RootLayout({
children,
sidebar, // 对应@sidebar槽
modal, // 对应@modal槽
toast, // 对应@toast槽
}: {
children: React.ReactNode;
sidebar: React.ReactNode;
modal: React.ReactNode;
toast: React.ReactNode;
}) {
return (
<html>
<body>
{toast}
{modal}
<div className="flex">
{sidebar}
<main>{children}</main>
</div>
</body>
</html>
);
}
二、核心匹配规则
每个 @xxx 槽位完全独立匹配当前访问的 URL 路径,和主路由 children、其他槽位的匹配逻辑完全无关,匹配规则和普通主路由完全一致。
1. 最高优先级:匹配槽位下对应路径的 page.tsx
比如访问 /post/123:
- 主路由找
app/post/[id]/page.tsx作为children @sidebar槽独立找app/@sidebar/post/[id]/page.tsx,存在就直接渲染
2. 次优先级:逐层向上找 default.tsx 兜底
如果匹配不到对应路径的 page.tsx,就从当前路径段开始逐层向上找父级的 default.tsx:
还是访问 /post/123,@sidebar 槽找不到 @sidebar/post/[id]/page.tsx:
- 先找
@sidebar/post/default.tsx→ 没有 - 再找
@sidebar/default.tsx→ 有就渲染
3. 最低优先级:无兜底则直接 404
如果从当前路径到 @xxx 根目录都找不到 page.tsx 或 default.tsx,哪怕主路由 children 存在,整个页面直接返回 404。
特殊文件作用(每个槽位独立生效)
| 文件 | 作用范围 |
|---|---|
@xxx/page.tsx | 仅服务于根路径 / 的专属内容,不是全局默认 |
@xxx/default.tsx | 全局兜底内容,所有匹配不到专属 page.tsx 的路径都渲染它 |
@xxx/loading.tsx | 当前槽位独有的加载 UI,不影响其他槽位和主内容 |
@xxx/error.tsx | 当前槽位独有的错误边界,槽位报错不会导致整个页面崩溃 |
@xxx/layout.tsx | 当前槽位下所有子路径的公共布局 |
三、组合规则(T3 项目必备)
并行路由 90% 的生产级用法都是和拦截路由配合,实现「模态框随 URL 变化、刷新保留状态、关闭回退」的标准交互。
拦截路由的路径前缀规则
| 前缀 | 匹配逻辑 | 典型用法 |
|---|---|---|
(.) | 匹配同级路径 | @modal/(.)user/[id]/page.tsx:从同级页面点进 /user/123 时弹模态框 |
(..) | 匹配上一级路径 | @modal/(..)post/[id]/page.tsx:从子页面点进 /post/123 时弹模态框 |
(..)(..) | 匹配上两级路径 | 同理 |
四、导航行为规则
-
客户端软导航(Link 点击、router.push):所有槽位都会重新匹配新路径,已匹配的内容如果没有变化不会重新渲染(比如通用侧边栏在
/dashboard和/dashboard/settings都匹配default.tsx,跳转时不会重渲) -
硬刷新/服务端导航:拦截路由的前缀(
(.)/(..))失效,槽位会按普通路径匹配,找不到就走default.tsx兜底
举个例子:从
/dashboard点击/user/123触发软导航,@modal槽匹配(.)user/[id]/page.tsx弹框;刷新/user/123时,@modal找不到@modal/user/[id]/page.tsx,走default.tsx返回空,主路由渲染app/user/[id]/page.tsx的独立页面,符合用户预期。
五、避坑指南(Top5 新手必踩)
-
❌ 忘加
default.tsx:路径不匹配直接 404,哪怕主路由存在 -
❌ 把
@xxx/page.tsx当成全局默认:它仅服务于根路径/,其他路径找不到专属page.tsx只会用default.tsx -
❌ 槽位文件夹没加
@前缀:会被当成普通路由,生成/sidebar这类可访问路径,不是槽位 -
❌ 根 Layout 的 props 名和槽位名不一致:比如槽位叫
@userModal,props 传usermodal,大小写敏感,渲染不出来 -
❌ 槽位里写
layout.tsx忘传 children:槽位的布局和普通布局一样,必须接收children渲染子内容
六、T3 项目最佳实践
标准后台结构
app/
├── layout.tsx // 接收children、sidebar、modal、toast四个prop
├── @sidebar/
│ ├── default.tsx // 全局通用侧边栏(放菜单、用户信息)
│ ├── page.tsx // 根路径/专属侧边栏(放欢迎引导)
│ └── settings/
│ └── page.tsx // /settings路径专属侧边栏(放设置分类)
├── @modal/
│ ├── default.tsx // 兜底返回null,默认不显示
│ └── (.)user/[id]/page.tsx // 拦截同级的/user/[id],弹用户详情模态框
├── @toast/
│ └── default.tsx // 全局通知组件,永远渲染,不用其他配置
├── dashboard/
│ └── page.tsx
├── settings/
│ └── page.tsx
├── user/
│ └── [id]/page.tsx // 直接访问/user/[id]时的独立页面
└── page.tsx
对应渲染结果
| 访问路径 | 渲染内容 |
|---|---|
/ | 侧边栏用根路径专属,主内容用根 page,模态框隐藏 |
/dashboard | 侧边栏用全局 default,主内容用 dashboard/page,模态框隐藏 |
/settings | 侧边栏用 settings 专属,主内容用 settings/page,模态框隐藏 |
从/dashboard 点进 /user/123 | 侧边栏不变,主内容还是 dashboard/page,模态框弹用户详情,URL 变成 /user/123 |
刷新 /user/123 | 模态框隐藏,主内容用 user/[id]/page 的独立页面 |