当您事先不知道确切的路由段名称,并希望从动态数据创建路由时,您可以使用动态路由段(Dynamic Segments),这些路由段可以在请求时填充或在构建时预渲染。
动态路由段可以通过将文件夹名称包裹在方括号中来创建:[folderName]。例如,一个博客可以包含以下路由 app/blog/[slug]/page.js,其中 [slug] 是用于博客文章的动态路由段。
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <div>My Post: {slug}</div>
}export default async function Page({ params }) {
const { slug } = await params
return <div>My Post: {slug}</div>
}动态路由段作为 params prop 传递给 layout、page、route 和 generateMetadata 函数。
| 路由 | 示例 URL | params |
|---|---|---|
app/blog/[slug]/page.js | /blog/a | { slug: 'a' } |
app/blog/[slug]/page.js | /blog/b | { slug: 'b' } |
app/blog/[slug]/page.js | /blog/c | { slug: 'c' } |
在客户端组件的 page 中,可以借助 use Hook 访问来自 props 的动态路由段。
'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>
)
}'use client'
import { use } from 'react'
import { useParams } from 'next/navigation'
export default function BlogPostPage({ params }) {
const { slug } = use(params)
return (
<div>
<p>{slug}</p>
</div>
)
}另外,客户端组件可以使用 useParams Hook 在客户端组件树中的任何位置访问 params。
动态路由段可以通过在方括号内添加省略号 [...folderName] 来扩展,以 捕获所有 后续路由段。
例如,app/shop/[...slug]/page.js 将匹配 /shop/clothes,但也会匹配 /shop/clothes/tops、/shop/clothes/tops/t-shirts 等。
| 路由 | 示例 URL | params |
|---|---|---|
app/shop/[...slug]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[...slug]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[...slug]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
捕获所有路由段可以通过将参数包含在双重方括号中来变为 可选:[[...folderName]]。
例如,app/shop/[[...slug]]/page.js 除了匹配 /shop/clothes、/shop/clothes/tops、/shop/clothes/tops/t-shirts 之外,也 将匹配 /shop。
捕获所有 和 可选捕获所有 路由段之间的区别在于,对于可选路由段,不带参数的路由也会被匹配(以上示例中的 /shop)。
| 路由 | 示例 URL | params |
|---|---|---|
app/shop/[[...slug]]/page.js | /shop | { slug: undefined } |
app/shop/[[...slug]]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[[...slug]]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[[...slug]]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
使用 TypeScript 时,您可以根据配置的路由段为 params 添加类型——分别在 page、layout 和 route 中使用 PageProps<'/route'>、LayoutProps<'/route'> 或 RouteContext<'/route'> 来为 params 定义类型。
路由的 params 值被类型化为 string、string[] 或 undefined(对于可选捕获所有路由段),因为它们的值直到运行时才可知。用户可以在地址栏中输入任何 URL,这些宽泛的类型有助于确保您的应用程序代码能够处理所有这些可能的情况。
| 路由 | params 类型定义 |
|---|---|
app/blog/[slug]/page.js | { slug: string } |
app/shop/[...slug]/page.js | { slug: string[] } |
app/shop/[[...slug]]/page.js | { slug?: string[] } |
app/[categoryId]/[itemId]/page.js | { categoryId: string, itemId: string } |
如果您正在处理一个 params 只能具有固定数量有效值的路由,例如具有已知语言代码集的 [locale] 参数,您可以使用运行时验证来处理用户可能输入的任何无效参数,并让应用程序的其余部分使用来自已知集的更窄类型。
import { notFound } from 'next/navigation'
import type { Locale } from '@i18n/types'
import { isValidLocale } from '@i18n/utils'
function assertValidLocale(value: string): asserts value is Locale {
if (!isValidLocale(value)) notFound()
}
export default async function Page(props: PageProps<'/[locale]'>) {
const { locale } = await props.params // locale is typed as string
assertValidLocale(locale)
// locale is now typed as Locale
}params prop 是一个 Promise。您必须使用 async/await 或 React 的 use 函数来访问这些值。
params 是一个同步 prop。为了帮助实现向后兼容性,您仍然可以在 Next.js 15 中同步访问它,但此行为将来会被弃用。generateStaticParams 使用generateStaticParams 函数可用于在构建时 静态生成 路由,而不是在请求时按需生成。
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}当在 generateStaticParams 函数内部使用 fetch 时,请求会被 自动去重。这避免了对相同数据的 Layouts、Pages 和其他 generateStaticParams 函数进行多次网络调用,从而加快了构建时间。