MDX 渲染栈笔记:remark、rehype 和代码高亮如何协作
基于 HUAN Lab 的 MDX 渲染实现,梳理 next-mdx-remote、remark-gfm、rehype-slug 和 rehype-pretty-code 在文章页面中的职责分工。
MDX 的好处是让文章保持 Markdown 的写作体验,同时保留 React 组件扩展空间。但在真正落地时,问题不只是“能不能渲染”,还包括表格、代码块、标题锚点、组件样式和文章页结构如何稳定协作。HUAN Lab 当前把这部分集中在 lib/mdx.ts 和文章详情页里处理。[source-mdx][source-post-page]
对个人博客来说,MDX 渲染栈应该尽量薄:插件各司其职,页面只关心渲染结果,文章作者不需要理解整条 AST 管线。
渲染入口
renderMdx 使用 next-mdx-remote/rsc 的 compileMDX,并把传入的正文编译成 React Server Component 可消费的内容。[source-mdx] 这里的 parseFrontmatter 被设为 false,原因可以从整体结构推断出来:frontmatter 已经在 lib/posts.ts 中由 gray-matter 解析和校验,MDX 渲染层只需要处理正文。[source-posts][source-mdx]
export async function renderMdx(source: string) {
const result = await compileMDX({
source,
components: mdxComponents,
options: {
parseFrontmatter: false,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeSlug,
[
rehypePrettyCode,
{
theme: "github-light",
keepBackground: false
}
]
]
}
}
});
return result.content;
}这个拆分让内容解析和内容渲染各自保持单一职责:lib/posts.ts 负责元数据和索引,lib/mdx.ts 负责把正文转成可展示的 React 内容。[source-posts][source-mdx]
remark 处理 Markdown 能力
HUAN Lab 在 remark 阶段使用 remark-gfm。[source-mdx] GFM 通常覆盖博客写作里很实用的一组 Markdown 能力,例如表格、任务列表和删除线。这里需要注意的是,当前代码只证明项目启用了 remark-gfm,具体哪些语法会被文章实际使用,要以内容文件为准。
从写作规范看,博客文章希望正文至少包含标题、代码块、列表和引用块。[source-writing-guide] 这和启用 GFM 的方向一致:文章作者可以用更完整的 Markdown 表达技术说明,而不是把所有复杂内容都改写成 React 组件。
rehype 处理 HTML 层增强
rehype 阶段启用了两个插件:rehype-slug 和 rehype-pretty-code。[source-mdx]
rehype-slug 的角色是给标题节点补充可定位的 id。文章详情页同时会渲染 TableOfContents,而 lib/posts.ts 也会从正文中提取 ## 和 ### 标题生成目录数据。[source-posts][source-post-page] 综合这些代码可以推断,标题 id 和目录链接的一致性,是文章阅读体验的基础。
rehype-pretty-code 用于代码高亮,配置中选择了 github-light 主题,并关闭背景保留。[source-mdx] 这说明代码块视觉风格更靠近浅色技术文档,而不是深色编辑器截图式展示。
页面层如何消费渲染结果
文章详情页的流程很直接:通过 slug 找到文章,调用 renderMdx(post.content) 得到内容,然后放到 .prose 容器里展示。[source-post-page] 同一个页面还会渲染阅读进度、文章元信息、标签、目录和上一篇下一篇导航。[source-post-page]
这个页面没有直接解析 Markdown,也没有关心代码高亮插件细节。它只消费内容层给出的结构化字段:
post.title用于页面标题post.description用于摘要post.tags用于标签链接post.headings用于目录post.content用于 MDX 渲染
这是一种比较舒服的分工。渲染链路出了问题时,可以优先检查 lib/mdx.ts;元数据或目录出了问题时,可以优先检查 lib/posts.ts。
写作时的实践建议
基于当前实现,写 MDX 时可以遵守几个简单约定:
- 用
##和###组织正文层级,避免目录过深 - 代码块写明语言,方便高亮器识别
- 表格适合放小规模对比,不要把大数据表塞进文章
- 引用块用于强调判断,不要替代正文解释
## 构建检查
```bash
npm run build构建通过不等于文章已经完美,但它能先排除语法和元数据错误。
需要留意的是,上面示例只是写作格式示意,不代表项目额外实现了代码块标题、行号或复制按钮。当前能从代码中确认的是 GFM、标题 slug 和 pretty-code 高亮。[source-mdx]
## 小结
HUAN Lab 的 MDX 渲染栈没有追求复杂功能,而是围绕技术博客最常见的阅读需求:Markdown 扩展、标题锚点、代码高亮和页面目录。这样的实现足够轻,也方便后续迭代。等文章数量和类型继续增加后,再考虑给代码块增加文件名、复制按钮或交互组件,会比一开始就堆满插件更稳。
## 参考资料
1. [source-mdx] `lib/mdx.ts`,HUAN Lab 的 MDX 编译、remark 与 rehype 插件配置。
2. [source-posts] `lib/posts.ts`,文章正文、标题提取和 frontmatter 处理逻辑。
3. [source-post-page] `app/posts/[slug]/page.tsx`,文章详情页的渲染流程与页面组成。
4. [source-writing-guide] `docs/writing-guide.md`,HUAN Lab 的文章写作结构建议。