MDX文档开发实战:提升技术写作与组件集成效率
先扔个最简单的例子:MDX 真的能写 React 组件
我第一次用 MDX 的时候,以为它只是 Markdown 加点语法糖。结果一上手才发现,这玩意儿直接把 Markdown 文件变成了 React 组件文件——你可以在里面写 JSX、导入组件、甚至用 useState。
比如下面这个 blog.mdx:
import { Callout } from './components/Callout';
# 欢迎使用 MDX
普通段落。
<Callout type="tip">
这是自定义的提示组件,完全用 React 写的!
</Callout>
还可以在代码里用状态:
export const Counter = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
};
<Counter />
没错,上面这段代码在支持 MDX 的构建工具(比如 Next.js + @next/mdx 或 Vite + @mdx-js/rollup)里能直接跑。亲测有效,不用额外配置什么奇怪的 loader。
但注意:默认导出(default export)会被 MDX 编译器自动处理成页面内容,所以如果你想在 MDX 里导出一个函数给外部用,得用命名导出(named export),比如 export const Counter = ...。
踩坑提醒:这三点一定注意
我折腾 MDX 踩过不少坑,挑三个最痛的说说:
- 组件必须显式导入:MDX 不会自动识别你用了哪些组件。比如你写了
<Button>,但没 import,直接报错。别指望它像某些框架那样自动全局注册。 - 作用域问题很隐蔽:你在 MDX 顶部定义的变量或函数,只能在当前文件内用。如果你在多个 MDX 文件里复用逻辑,建议抽成独立的 JS 模块再 import,别图省事复制粘贴。
- 样式隔离容易翻车:MDX 里的 HTML 元素(比如
<p>、<h1>)默认没有样式。如果你的全局 CSS 没有覆盖这些标签,或者用了 CSS-in-JS 但没传 theme,排版会丑到怀疑人生。建议统一用一个MDXProvider包裹,注入基础样式。
举个例子,用 MDXProvider 统一处理标题样式:
// layouts/MDXLayout.js
import { MDXProvider } from '@mdx-js/react';
const components = {
h1: (props) => <h1 style={{ fontSize: '2rem', color: '#333' }} {...props} />,
p: (props) => <p style={{ lineHeight: 1.6 }} {...props} />,
};
export default function MDXLayout({ children }) {
return <MDXProvider components={components}>{children}</MDXProvider>;
}
然后在你的页面里包一层就行。这样所有 MDX 文件里的 h1、p 都会自动带上样式,不用每个文件重复写。
这个场景最好用:动态文档 + 交互示例
MDX 最爽的场景是什么?就是写技术文档时,直接嵌入可交互的 Demo。比如你想展示一个 API 调用结果,不用截图,直接写个组件调接口:
import { useEffect, useState } from 'react';
const ApiDemo = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://jztheme.com/api/data')
.then(res => res.json())
.then(setData)
.finally(() => setLoading(false));
}, []);
if (loading) return <div>加载中...</div>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
};
<ApiDemo />
这种写法比静态截图强太多了——读者能看到真实数据结构,还能自己改代码试试(如果配合 CodeSandbox 之类就更香)。我之前给团队写内部组件库文档,全靠这招,反馈特别好。
不过要注意:别在 MDX 里放太多复杂逻辑。MDX 文件本质还是文档,不是业务代码。逻辑一多,维护起来反而比单独写页面还麻烦。我的经验是:交互部分控制在 20 行以内,复杂逻辑拆成独立组件。
高级技巧:用 frontmatter 控制元数据
MDX 支持 YAML frontmatter,和 Jekyll、Hexo 那套一样。你可以用它存标题、作者、发布日期等信息:
---
title: "MDX 实战指南"
author: "老张"
published: true
---
然后在布局组件里读取:
// pages/blog/[slug].js (Next.js 示例)
import { getMDXComponent } from 'mdx-bundler';
import { readFileSync } from 'fs';
export async function getStaticProps({ params }) {
const source = readFileSync(./posts/${params.slug}.mdx, 'utf8');
const { code, frontmatter } = await bundleMDX(source);
return { props: { code, frontmatter } };
}
export default function BlogPost({ code, frontmatter }) {
const Component = getMDXComponent(code);
return (
<article>
<h1>{frontmatter.title}</h1>
<p>作者:{frontmatter.author}</p>
<Component />
</article>
);
}
这里注意:不同构建工具处理 frontmatter 的方式不一样。比如 mdx-bundler 会自动解析,而 @mdx-js/loader 可能需要额外插件。我建议直接用 mdx-bundler,它对 frontmatter 的支持最省心,而且和 Next.js 配合起来特别顺。
最后一点:别被“完美方案”绑架
MDX 虽然强大,但也不是万能的。我见过有人硬要把整个营销页塞进 MDX,结果一堆状态管理、路由跳转写得乱七八糟。记住:MDX 的核心优势是“文档 + 轻量交互”,别把它当通用页面框架用。
另外,MDX 的构建速度比纯 Markdown 慢,因为要编译 JSX。如果你的项目有上百篇文档,记得开缓存(比如 mdx-bundler 的 cache 选项),不然每次 dev server 启动都得等半分钟。
以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多——比如结合 remark 插件自动加目录、用 rehype 处理图片懒加载——后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

暂无评论