动态路由与动态 metadata

[id] 动态段保持 Server Component:generateStaticParams 预生成已知 id,generateMetadata 与页面同文件;不存在的 id 走 notFound()

详情页的真实代码(本站 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