MDX 与内容系统
MDX 与内容系统 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
课程正文如果硬编码进组件,改一个错别字都要动代码。本课用 MDX 把内容变成文件:正文、代码、提示写进 .mdx,元数据走 frontmatter 校验,目录从标题自动生成,坏内容和死链要有明确的处理姿势。
问题场景
你给课程页加「本章小结」,做法是在 page.tsx 里塞一段 <p>。两周后正文里散落了 20 处这种「内容钉在代码里」的补丁——改文案要动组件、不会写代码的人改不了、也没法像文件一样做 diff 和备份。内容该是内容,代码该是代码。
核心解释
MDX = Markdown + 少量组件。 课程正文存成文件(如 content/courses/nextjs/n16-mdx-content-system/lessons/main.mdx),frontmatter 写元数据:
---
title: 流式渲染与加载边界
est_minutes: 25
knowledge_points: [Suspense, loading.tsx]
---
## 核心解释
流式渲染让快内容先走……
<aside>提示:本节代码在 app/courses 下。</aside>
元数据必须校验,坏数据挡在渲染前。 frontmatter 是人肉编辑的 YAML,一个 typo 就能让页面拿到 undefined。用 zod 在加载层把关:
// lib/content/loader.ts(节选)
import fs from "node:fs";
import matter from "gray-matter";
import { z } from "zod";
const FrontmatterSchema = z.object({
title: z.string().min(1),
est_minutes: z.number().int().positive().max(120),
knowledge_points: z.array(z.string()).default([]),
});
export function loadLesson(mdxPath: string) {
const raw = fs.readFileSync(mdxPath, "utf8");
const { data, content } = matter(raw);
const parsed = FrontmatterSchema.safeParse(data);
if (!parsed.success) {
// 内容系统的原则:坏内容要「可见地失败」,而不是渲染出空页
throw new Error(`frontmatter 不合法: ${mdxPath} ${parsed.error.message}`);
}
return { meta: parsed.data, content };
}
目录从标题长出来,内部引用要体检。 渲染前先剥离代码围栏,再用正则扫 ^##? 抽出标题文本和锚点,生成右侧目录;正文里指向其他课程、课时的链接,在内容校验阶段逐个核对目标存在——指向不存在课时的死链要么在构建时报错拦下,要么降级标注「待补充」,别等用户点进去 404。
三条纪律:
- 无效内容分场景处理:构建期能发现的(frontmatter 缺字段、死链、围栏不闭合)直接报错;运行时才出现的降级展示并留日志。
- MDX 里用到的自定义组件(提示框、对照表)在渲染端统一注册,正文只管使用。
- 内容文件进 git——改文案也有 diff 可查,这是内容离开组件库的回报。
示例拆解
在本机工程里:
- 打开 content/courses/ 下任意课程的 main.mdx,删掉 frontmatter 的 title 字段,运行内容校验(npm run content:check 或启动 dev)——应在加载层收到明确报错而不是白屏。
- 恢复 title,在正文里写一个指向不存在课程的路由链接,跑校验确认被标记为死链。
- 打开课程页,确认右侧目录条目与正文
##标题一一对应,代码块里的#注释没有混进目录。
关键点:npm run content:check 是内容质量的门禁——它跑得越勤,坏内容漏到页面的机会越少。
动手任务
在本机工程里完成:
- 给 loader 的 schema 加一条规则(如 est_minutes 上限),构造越界数据验证报错信息可读。
- 从一份 main.mdx 生成目录数组,打印确认层级与锚点正确。
- 给内部链接写一个核验函数:有效目标通过、无效目标报错,接进内容校验脚本。
常见错误
- frontmatter 不校验直接用:拼写错误静默变成 undefined,页面某个角落悄悄坏掉。
- 目录正则扫到了代码块里的井号注释:先剥离围栏再抽标题,否则目录里混进假标题。
- 死链只在运行时暴露:用户点击才 404;链接核验必须进构建或校验流程。
- 正文里堆复杂 JSX 逻辑:内容文件的编辑门槛反而变高;正文里只该出现少量展示型组件。
检查与复盘
自查清单:
- 删改 frontmatter 字段会得到明确报错而非白屏
- 目录、提示、代码高亮都来自内容文件本身
- 内容校验脚本已进入日常流程(启动或提交前)
参考资料
- Next.js 文档:MDX —— MDX 集成的官方指南
- MDX 官方文档 —— 语法与组件机制
- gray-matter —— frontmatter 解析库
读完这一课了?
标记完成后,工作台会安排接下来的学习。