返回文章列表

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/rsccompileMDX,并把传入的正文编译成 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-slugrehype-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 的文章写作结构建议。