首页 / Next.js 16 入门教程 / 路由进阶

Next.js 16 入门教程

路由进阶

本教程共 42 篇 · 第 4 篇 · 更新于 2026-07-30 · 约 9 分钟阅读

Next.jsNext.js 16 入门教程动态路由路由组并行路由拦截路由

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-worldslug 就是 "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
/docsundefined
/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 里。

路由组的典型用途:

  1. 按业务模块分组:营销页、后台管理、用户中心
  2. 共享布局:同组路由共用一个 layout
  3. 多根布局:不同组可以有完全不同的根布局

多根布局

删除根 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 可分享)
  • 刷新页面跳转到独立页面
  • 浏览器前进后退行为正确

小结

这一章我们学了四种进阶路由技巧:

  1. 动态路由[slug][...slug][[...slug]]
  2. 路由组(group) 组织代码,不影响 URL
  3. 并行路由@slot 同时渲染多个模块
  4. 拦截路由(.)folder 在当前页渲染其他路由

这些技巧是构建复杂 Next.js 应用的基础。下一章,我们来学习链接与导航。