Next.js 动态路由详解
动态路由是 Next.js 的核心功能之一,它允许你根据动态参数创建页面,非常适合构建博客、电商产品页、用户资料等需要根据数据动态生成的页面。
基本概念
Next.js 的动态路由是通过文件系统实现的,你只需要在 pages
目录下创建特殊命名的文件或文件夹:
pages/posts/[id].js
- 匹配/posts/1
,/posts/abc
等pages/categories/[category]/[id].js
- 匹配/categories/books/123
基本使用示例
1. 单参数动态路由
// pages/posts/[id].js
import { useRouter } from 'next/router'
export default function Post() {
const router = useRouter()
const { id } = router.query
return <div>Post ID: {id}</div>
}
应用场景:博客文章详情页,根据ID从API或数据库获取对应文章内容。
2. 多段动态路由
// pages/products/[category]/[productId].js
export default function Product() {
const { category, productId } = useRouter().query
return (
<div>
<h1>Product Details</h1>
<p>Category: {category}</p>
<p>Product ID: {productId}</p>
</div>
)
}
应用场景:电商网站产品页,按分类和产品ID展示商品详情。
高级用法
1. 预渲染与数据获取
Next.js 允许在构建时或请求时预渲染动态路由页面。
// pages/posts/[id].js
export async function getStaticPaths() {
// 获取所有可能的ID
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
// 返回路径数组
const paths = posts.map(post => ({
params: { id: post.id.toString() }
}))
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
// 根据ID获取文章数据
const res = await fetch(`https://api.example.com/posts/${params.id}`)
const post = await res.json()
return { props: { post } }
}
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}
应用场景:静态博客,在构建时生成所有文章页面,提供最佳性能。
2. 捕获所有路由
// pages/docs/[...slug].js
export default function Docs() {
const { slug } = useRouter().query
return (
<div>
<h1>Documentation Page</h1>
<p>Current path: {slug?.join('/')}</p>
</div>
)
}
访问 /docs/a/b/c
会得到 slug: ['a', 'b', 'c']
应用场景:文档网站,支持多级目录结构。
3. 可选捕获所有路由
// pages/blog/[[...slug]].js
export default function Blog() {
const { slug } = useRouter().query
return (
<div>
{slug ? (
<p>Viewing blog post: {slug.join('/')}</p>
) : (
<p>Viewing blog homepage</p>
)}
</div>
)
}
这会匹配 /blog
, /blog/a
, /blog/a/b
等。
应用场景:博客首页和文章详情页使用同一个组件。
实际应用场景示例
场景1:用户个人资料页
// pages/users/[username].js
import { getUserProfile } from '../../lib/api'
export async function getServerSideProps({ params }) {
try {
const user = await getUserProfile(params.username)
return { props: { user } }
} catch (error) {
return { notFound: true }
}
}
export default function UserProfile({ user }) {
return (
<div className="profile">
<img src={user.avatar} alt={user.name} />
<h1>{user.name}</h1>
<p>{user.bio}</p>
{/* 其他个人资料信息 */}
</div>
)
}
场景2:电商产品分类和详情页
// pages/shop/[...slugs].js
export async function getStaticPaths() {
// 获取所有产品路径
const products = await getAllProducts()
const paths = products.map(product => ({
params: { slugs: [product.category, product.slug] }
}))
return { paths, fallback: 'blocking' }
}
export async function getStaticProps({ params }) {
const [category, productSlug] = params.slugs
const product = await getProductDetails(category, productSlug)
return {
props: { product },
revalidate: 60 // ISR: 每60秒重新验证
}
}
export default function ProductPage({ product }) {
// 渲染产品详情
}
最佳实践
-
合理使用预渲染策略:
- 使用
getStaticProps
+getStaticPaths
构建时生成静态页面 - 使用
getServerSideProps
服务端渲染动态内容 - 使用
fallback: true
或'blocking'
处理新增内容
- 使用
-
处理未找到的情况:
export async function getStaticProps({ params }) { const post = await getPost(params.id) if (!post) { return { notFound: true } } return { props: { post } } }
-
类型安全(使用 TypeScript):
import { GetStaticPaths, GetStaticProps } from 'next' interface PostProps { post: { id: string title: string content: string } } export const getStaticPaths: GetStaticPaths = async () => { // ... } export const getStaticProps: GetStaticProps<PostProps> = async ({ params }) => { // ... }
动态路由是 Next.js 最强大的功能之一,合理使用可以构建出既保持良好SEO又能提供优秀用户体验的现代Web应用。