N19第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

页面元数据与索引

页面元数据与索引 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

课程页标题全是站名、描述永远同一句、无效课程返回 500——这些会让页面在浏览器标签、分享卡片和搜索引擎里失去身份。本课给课程和项目页配上准确的标题、描述和稳定链接,并理清「页面元数据」与「正文索引」的关系。

问题场景

你把课程页链接发到群里,预览卡片显示「ReactLab」加一句通用 slogan——对方看不出这是哪门课。标签页里开了五个课程页,标题全都一样,切换全靠猜。这些信息不是正文,是页面的「名片」:title、description、canonical URL。

核心解释

静态元数据:导出 metadata 对象。 全局兜底放在根 layout,子页面各自覆盖:

// app/layout.tsx(节选)
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: { default: "ReactLab", template: "%s | ReactLab" },
  description: "个人前端学习工作台",
};

动态元数据:generateMetadata 按内容生成。 课程页的标题描述来自数据本身:

// app/courses/[slug]/page.tsx(节选)
import type { Metadata } from "next";
import { notFound } from "next/navigation";

interface Props {
  params: Promise<{ slug: string }>;
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const course = await getCourse(slug);
  if (!course) return { title: "课程不存在" };
  return {
    title: course.title,                  // 套进模板:流式渲染与加载边界 | ReactLab
    description: course.summary,
    alternates: { canonical: `/courses/${slug}` },
  };
}

export default async function CoursePage({ params }: Props) {
  const { slug } = await params;
  const course = await getCourse(slug);
  if (!course) notFound();               // 渲染层同样要兜住
  return <article>{/* 课程正文 */}</article>;
}

三条要点:

  • 标题模板:layout 声明 template,子页只写自己的名字,站名自动拼接——标签页里一眼分清五个课程页。
  • 稳定链接:slug 是课程的身份,永不改变;canonical 告诉索引器「这个地址是正身」,带查询参数的副本不会被当成重复页面。
  • 无效页面走 notFound():数据查不到就触发 404 约定文件,返回明确的 404 状态码——比渲染一个内容为空的 200 页面诚实得多,搜索引擎也能正确丢弃死链。

关于索引再澄清一层:搜索引擎读的是渲染后的 HTML——元数据提供标题与描述,正文提供内容本身,两者都在服务端渲染输出里,这正是 App Router 服务端组件的天然优势。内容站还可以用 sitemap.ts / robots.ts 以代码方式声明「哪些页面值得收录」。

示例拆解

本机验证:

  1. 打开两门不同的课程页,标签页标题分别是「X | ReactLab」「Y | ReactLab」。
  2. 执行 curl -s localhost:3000/courses/n07-server-data-access | grep -o "<title>[^<]*</title>"——输出含课程名。
  3. 访问一个不存在的 slug——得到 404 页面,curl -s -o /dev/null -w "%{http_code}" 输出 404。
  4. 查看页面源码,确认 description 与课程 summary 一致。

关键点:元数据与正文必须来自同一次数据查询,否则「卡片说 A、页面演 B」的不一致早晚会发生。

动手任务

在本机工程里完成:

  1. 给根 layout 配 title template,给两门课程页配 generateMetadata,验证标签页标题差异。
  2. 给项目详情页补 canonical 与 description,用 curl 检查最终 HTML。
  3. 故意访问错误 slug,确认走 not-found 页面且状态码是 404 而非 200。

常见错误

  • generateMetadata 与页面各查一次数据、结果不一致:元数据说有这课,正文却渲染空;两处应复用同一个查询函数。
  • 在 generateMetadata 里抛异常:整页 500;数据缺失应返回占位元数据或调用 notFound()。
  • 标题没配 template:每页手工拼站名,漏拼就千篇一律。
  • 动态页面不兜 notFound():无效 slug 渲染 200 空页,搜索引擎索引一堆空壳。

检查与复盘

自查清单:

  • 每个课程、项目页的标签标题唯一且含自身名字
  • 主要页面的 description 准确,分享卡片可辨认
  • 无效链接返回 404 状态码与友好页面

参考资料

检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

想让每个课程页在搜索结果和分享卡片里显示自己的标题与描述,正确的做法是?

Q2多选中等(多选,全部选对才算正确)

关于 metadata 的继承与生成,下列说法正确的有?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...