App Router 与页面
App Router 与页面 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
App Router 的核心约定是「目录即 URL」。读完你应该能建出课程列表页和详情页,用动态段和 notFound() 处理无效课程,并用 Link 组件把页面连成可导航的流程。
问题场景
学习工作台需要两个页面:/courses 展示课程列表,/courses/react-state 展示某门课详情。没有路由配置文件可写——在 App Router 里,建目录就是建路由。
核心解释
目录结构直接映射 URL:
app/
├── page.tsx → /
└── courses/
├── page.tsx → /courses
└── [slug]/
└── page.tsx → /courses/react-state、/courses/anything
[slug] 是动态段:方括号里的名字成为参数,匹配 URL 中对应的那一段。页面组件从 Promise 里 await 出参数——这是 Next.js 15+ 的约定,params 不是普通对象:
// app/courses/[slug]/page.tsx
import { notFound } from "next/navigation";
const COURSES: Record<string, { title: string; desc: string }> = {
"react-state": { title: "React 状态管理", desc: "快照、reducer 与 Context" },
"app-router": { title: "App Router 入门", desc: "目录即 URL" },
};
export default async function CoursePage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const course = COURSES[slug];
if (!course) notFound(); // 渲染最近的 not-found 边界
return (
<main>
<h1>{course.title}</h1>
<p>{course.desc}</p>
</main>
);
}
无效课程调用 notFound(),它会渲染 not-found 边界。根级兜底放在 app/not-found.tsx,也可以在嵌套目录里放更近的兜底。配套建一个:
// app/not-found.tsx
import Link from "next/link";
export default function NotFound() {
return (
<main>
<h2>404 · 没有这门课</h2>
<Link href="/courses">回课程列表</Link>
</main>
);
}
站内导航用 Link 而不是 <a>:Link 做客户端路由切换(不整页刷新、预取目标页面),<a> 会触发完整加载,丢掉所有客户端状态。外部地址才用 <a>。
补全列表页就有了完整闭环:app/courses/page.tsx 遍历数据渲染每个 <Link>,详情页查不到就落进自定义 404。路由、兜底、导航三件事一次配齐。
示例拆解
本课要落地的完整文件清单:
app/courses/page.tsx:列表页,遍历 COURSES 数据为每门课渲染<Link href={"/courses/" + slug}>;app/courses/[slug]/page.tsx:详情页,await params 后查数据,查不到notFound();app/not-found.tsx:404 兜底,含返回列表的 Link;- 验证路径:首页 → 列表页(点链接切换,观察页面无整页刷新)→ 详情页;再手动输入
/courses/不存在看自定义 404。
动手任务
在项目里完成三个小改动:
- 建
/courses列表页和/courses/[slug]详情页,数据先用内存对象。 - 给详情页加
notFound()分支,并自定义app/not-found.tsx。 - 在首页加一个指向
/courses的 Link,并在浏览器里对比 Link 和<a>的导航差异(观察是否整页刷新)。
常见错误
- 把 params 当普通对象直接用:
params.slug在新版 Next.js 里拿不到值——params 是 Promise,必须await params再解构。 - 忘了写 page.tsx:目录存在但没有 page.tsx 就不是路由,访问会 404;每个 URL 叶子节点都需要 page。
- 站内导航用了
<a href>:功能上能跳,但丢掉了客户端路由、预取和状态保留;站内一律 Link。 - notFound() 之后还在往下渲染:notFound() 抛出特殊错误中断渲染,它后面的代码不会执行;类型收窄后安心写主逻辑即可。
检查与复盘
完成下方检查题确认理解。自查清单:
- 能画出目录结构和对应 URL 的映射表,包括动态段
- 详情页对无效参数有 notFound() 兜底,自定义 404 可见
- 站内链接全部是 Link,导航切换不整页刷新
参考资料
- Next.js 文档:布局与页面 —— 目录即路由的核心约定
- Next.js 文档:not-found 文件约定 —— 404 边界的写法
- Next.js 文档:Link 组件 —— 预取与客户端导航行为
读完这一课了?
标记完成后,工作台会安排接下来的学习。