Next.js 是基于 React 的全栈框架,其动态路由功能让我们能轻松构建支持参数化 URL 的页面(比如用户详情页
/users/123
、商品详情页/products/shoes
)。本文基于 Next.js App Router(app/
目录),通过简单易懂的方式,带你掌握动态路由的核心用法。
一、动态路由的基本概念
在 Next.js 中,动态路由 指的是页面路径中包含可变参数 (如 id
、slug
等),通过这些参数可以渲染不同的内容。例如:
/users/[id]
→ 对应用户 ID 为1
、2
、3
... 的详情页/posts/[slug]
→ 对应文章别名为hello-world
、nextjs-guide
... 的详情页
💡 什么是 slug?
Slug 是内容的友好 URL 别名(如
nextjs-guide
),用于替代生硬的 ID。例如将文章标题"如何学习 Next.js"转为 URL 路径:how-to-learn-nextjs
。
二、App Router 中的动态路由写法
1. 文件命名规则
在 app
目录下,动态参数用方括号 [ ]
作为文件夹名 ,页面文件必须命名为 page.tsx
(或 page.js
):
- 单参数页面 :
app/users/[id]/page.tsx
→ 访问/users/123
时,id = '123'
- 多参数页面 :
app/posts/[category]/[slug]/page.tsx
→ 访问/posts/tech/nextjs-guide
时,参数为{ category: 'tech', slug: 'nextjs-guide' }
✅ 关键区别 :
Pages Router 是 pages/users/[id].tsx
,而 App Router 是 app/users/[id]/page.tsx
。
三、获取动态路由参数
在 App Router 中,动态参数通过 params
对象传入页面组件(无需 Hook):
示例:用户详情页
tsx
// app/users/[id]/page.tsx
export default async function UserDetail({ params }: { params: { id: string } }) {
const { id } = await params; // id 类型为 string
// 模拟从 API 获取用户数据(服务端执行!)
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const user = await res.json();
if (!user.name) {
return <div>用户不存在</div>;
}
return (
<div style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
<h1>👤 用户详情</h1>
<p><strong>ID:</strong> {user.id}</p>
<p><strong>姓名:</strong> {user.name}</p>
<p><strong>邮箱:</strong> {user.email}</p>
</div>
);
}
✅ 优势:
- 数据在服务端获取,自动 SSR,SEO 友好;
- 无需
useEffect
或客户端 loading;- 参数直接通过函数参数传入,类型清晰。
需要注意的是,获取参数,这里用了await params,这是next15的新特性,因为next15中params是一个Promise,所以需要使用await来获取。为了兼容老版本,next15也允许不加await来访问参数,但是未来可能会弃用这种方式。
在client组件种获取参数 需要引入react的use钩子
javascript
'use client'
import { use } from 'react'
export default function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = use(params)
return (
<div>
<p>{slug}</p>
</div>
)
}
四、多参数动态路由示例
tsx
// app/posts/[category]/[slug]/page.tsx
export default function BlogPost({ params }: { params: { category: string; slug: string } }) {
const { category, slug } = await params;
return (
<div>
<h1>文章:{slug}</h1>
<p>分类:{category}</p>
</div>
);
}
访问 /posts/tech/nextjs-guide
,页面将显示:
文章:nextjs-guide
分类:tech
五、生成动态路由链接
使用 next/link
的 <Link>
组件跳转,和 Pages Router 用法一致:
tsx
// app/page.tsx(首页)
import Link from 'next/link';
export default function HomePage() {
const users = [
{ id: '1', name: '张三' },
{ id: '2', name: '李四' },
];
return (
<div style={{ padding: '2rem' }}>
<h2>用户列表</h2>
{users.map(user => (
<div key={user.id} style={{ margin: '0.5rem 0' }}>
<Link href={`/users/${user.id}`}>
查看 {user.name} 的详情
</Link>
</div>
))}
</div>
);
}
🔗 注意 :
<Link>
的href
仍使用字符串拼接,和 App Router 无关。
六、可选参数与 Catch-all 路由
1. Catch-all Segments(匹配任意深度)
[...slug]/page.tsx
:匹配/docs
、/docs/a
、/docs/a/b
/docs
→params.slug = []
/docs/a
→params.slug = ['a']
/docs/a/b
→params.slug = ['a', 'b']
tsx
// app/docs/[...slug]/page.tsx
export default function DocsPage({ params }: { params: { slug: string[] } }) {
const path = params.slug.join('/');
return <p>文档路径:{path || '首页'}</p>;
}
2. Optional Catch-all(可选)
用双括号 [[...slug]]
表示参数可选(不匹配根路径):
app/docs/[[...slug]]/page.tsx
/docs
→slug = []
/docs/a
→slug = ['a']
七、完整实战:用户详情页(App Router)
1. 项目结构
bash
app/
layout.tsx # 全局布局
page.tsx # 首页(用户列表)
users/
[id]/
page.tsx # 用户详情页
2. 首页:用户列表 + 跳转链接
tsx
// app/page.tsx
import Link from 'next/link';
export default async function HomePage() {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await res.json();
return (
<div style={{ padding: '2rem' }}>
<h1>👥 用户列表</h1>
<ul>
{users.map((user: any) => (
<li key={user.id} style={{ margin: '0.5rem 0' }}>
<Link href={`/users/${user.id}`} style={{ color: '#0070f3' }}>
{user.name}
</Link>
</li>
))}
</ul>
</div>
);
}
3. 用户详情页(如上文所示)
访问 /users/1
,即可看到服务端渲染的用户信息,无白屏、SEO 友好!
八、总结:App Router 动态路由核心要点
项目 | 说明 |
---|---|
路由定义 | app/[param]/page.tsx |
获取参数 | 通过组件 props 的 params 对象 |
数据获取 | 直接在 page.tsx 中 await fetch() (服务端执行) |
跳转链接 | 仍用 <Link href="/users/1"> |
参数类型 | 始终为 string 或 string[] (Catch-all) |
SEO | ✅ 默认 SSR,内容直接嵌入 HTML |
九、为什么 App Router 更推荐?
- 🚀 更简洁 :无需
getServerSideProps
,数据获取更直观 - 🔒 更安全:服务端组件默认,敏感逻辑不暴露给客户端
- 🌐 SEO 友好:HTML 直出内容,搜索引擎轻松抓取
- 🧩 组合灵活 :配合
layout.tsx
、loading.tsx
、error.tsx
实现完整体验
掌握 App Router 的动态路由,你就能轻松构建博客、电商、用户中心等现代 Web 应用!🚀