路由进阶
本教程共 42 篇 · 第 4 篇 · 更新于 2026-07-30 · 约 9 分钟阅读
4. 路由进阶
本节目标:学会动态路由、路由组、并行路由和拦截路由,掌握构建复杂布局的核心技巧。
动态路由
动态路由用方括号命名文件夹,从 URL 中提取参数。
单段参数
// app/blog/[slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <div>文章:{slug}</div>
}
访问 /blog/hello-world,slug 就是 "hello-world"。
Catch-all 段
三个点加方括号,捕获所有后续路径:
// app/shop/[...slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string[] }>
}) {
const { slug } = await params
// /shop/clothes/shirts → slug = ['clothes', 'shirts']
return <div>路径:{slug.join('/')}</div>
}
可选 Catch-all
双层方括号让参数可选:
// app/docs/[[...slug]]/page.tsx
// 匹配 /docs、/docs/a、/docs/a/b
| 路径 | params.slug |
|---|---|
/docs | undefined |
/docs/a | ['a'] |
/docs/a/b | ['a', 'b'] |
generateStaticParams
想让动态路由在构建时预渲染,导出 generateStaticParams:
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
.then(res => res.json())
return posts.map((post: any) => ({
slug: post.slug,
}))
}
构建时,Next.js 会为每个 slug 生成静态 HTML。
路由组
路由组用括号包裹文件夹名,不影响 URL,只用于代码组织:
app/
├── (marketing)/
│ ├── layout.tsx
│ └── page.tsx # URL: /
├── (shop)/
│ ├── cart/
│ │ └── page.tsx # URL: /cart
│ └── account/
│ └── page.tsx # URL: /account
(marketing) 和 (shop) 不出现在 URL 里。
路由组的典型用途:
- 按业务模块分组:营销页、后台管理、用户中心
- 共享布局:同组路由共用一个 layout
- 多根布局:不同组可以有完全不同的根布局
多根布局
删除根 app/layout.tsx,在每个路由组里放自己的 layout:
app/
├── (marketing)/
│ ├── layout.tsx # 包含 <html><body>
│ └── page.tsx
└── (admin)/
├── layout.tsx # 包含 <html><body>
└── page.tsx
跨根布局导航会触发完整页面刷新,而不是客户端跳转。
并行路由
并行路由让你在同一页面同时渲染多个独立模块。用 @folder 命名:
app/
├── layout.tsx
├── @analytics/
│ └── page.tsx
├── @team/
│ └── page.tsx
└── page.tsx
@analytics 和 @team 是”插槽”(slot),不是路由段,不影响 URL。
Layout 接收插槽作为 props:
export default function Layout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
默认兜底
当某个插槽没有匹配的路由时,Next.js 渲染 default.tsx:
// app/@analytics/default.tsx
export default function Default() {
return null
}
Next.js 16 的破坏性变更
从 Next.js 16 起,所有并行路由槽位必须有
default.tsx文件,否则构建会失败。请为每个@slot都创建对应的default.tsx。
条件渲染
并行路由适合做条件 UI,比如按用户角色显示不同面板:
export default function Layout({
user,
admin,
}: {
user: React.ReactNode
admin: React.ReactNode
}) {
const role = checkUserRole()
return role === 'admin' ? admin : user
}
拦截路由
拦截路由让你在当前路由渲染另一个路由的内容,常用于弹窗(Modal)。
拦截规则
| 语法 | 含义 |
|---|---|
(.)folder | 拦截同级路由 |
(..)folder | 拦截父级路由 |
(..)(..)folder | 拦截祖父级路由 |
(...)folder | 从根目录拦截 |
实现 Modal
场景:点击列表项弹出 Modal,刷新页面则跳转到详情页。
app/
├── @modal/
│ ├── (.)photo/
│ │ └── [id]/
│ │ └── page.tsx # 拦截路由,渲染 Modal
│ └── default.tsx # 默认返回 null
└── photo/
└── [id]/
└── page.tsx # 独立的详情页
拦截路由的内容:
// app/@modal/(.)photo/[id]/page.tsx
import { Modal } from '@/components/modal'
export default async function PhotoModal({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const photo = await getPhoto(id)
return (
<Modal>
<img src={photo.url} alt={photo.title} />
</Modal>
)
}
默认兜底:
// app/@modal/default.tsx
export default function Default() {
return null
}
Layout 里渲染插槽:
export default function Layout({
children,
modal,
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
<>
{children}
{modal}
</>
)
}
这样,从列表点击进入时,Modal 覆盖在当前页面上。刷新页面则直接显示详情页。
组合使用
并行路由 + 拦截路由 = 强大的弹窗系统:
- 并行路由提供弹窗容器
- 拦截路由在容器里渲染弹窗内容
- 支持深链接(URL 可分享)
- 刷新页面跳转到独立页面
- 浏览器前进后退行为正确
小结
这一章我们学了四种进阶路由技巧:
- 动态路由:
[slug]、[...slug]、[[...slug]] - 路由组:
(group)组织代码,不影响 URL - 并行路由:
@slot同时渲染多个模块 - 拦截路由:
(.)folder在当前页渲染其他路由
这些技巧是构建复杂 Next.js 应用的基础。下一章,我们来学习链接与导航。