动态路由与动态 metadata
[id] 动态段保持 Server Component:generateStaticParams 预生成已知 id,generateMetadata 与页面同文件;不存在的 id 走 notFound()
进入一条动态段详情
点进去看标签标题。详情页是 Server Component,标题由同文件的 generateMetadata 生成
详情页的真实代码(本站 app/router/dynamic-routes/[id]/page.tsx)
三个导出各管一件事:预生成哪些 id、每个 id 的标题、找不到怎么办
① generateStaticParams:已知 id 构建期生成
export function generateStaticParams() {
// 列出 data/user.ts 里的 6 个用户;
// 他们的详情在构建期就渲染成静态产物,不是每次请求现查
return users.map((user) => ({ id: String(user.id) }));
}
// dynamicParams 不写,默认 true:名单之外的 id 仍会进 Page,
// 由下面的 notFound() 兜底;改成 false 则名单外直接整段 404② generateMetadata:与 page 同文件,async 里 await params
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>; // Next 15 起 params 是 Promise
}): Promise<Metadata> {
const { id } = await params;
const user = getUserById(Number(id));
if (!user) {
// 404 页也给出标题,并禁止索引 —— 页面级 robots 的活例子
return { title: "用户未找到", robots: { index: false, follow: true } };
}
return {
title: `${user.name} - ${user.role}`,
description: user.bio,
openGraph: { title: `${user.name} - ${user.role} | ${SITE_NAME}`, type: "profile" },
};
}③ Page:notFound() 处理「没有这条」,而不是画空状态
export default async function Page({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const user = getUserById(Number(id));
if (!user) {
notFound(); // 交给本段 not-found.tsx,不进 error.tsx
}
return <UserDetail user={user} />;
}动态段不等于整页动态
已知 id 在构建期就生成好了
- generateStaticParams 列出这 6 个用户。他们的详情在构建期生成,不是每次请求现查一遍本地数组
- generateMetadata 与 page 写在同一个 Server 文件里。只有 page 不得不标成 "use client" 时,才把标题挪到同级 layout
- 名单之外的 id 仍会进这个页面(dynamicParams 默认开启),getUserById 找不到就 notFound(),而不是返回一份空白资料卡
- 水合之后才需要变的数据(聚焦重验证、轮询)去客户端取数专题,不要为了它把整条详情改成 Client