mdx中直接写html标签会被当作文本而非可渲染元素,因解析器默认禁用原生html以保障安全;需通过自定义react组件、allowdangeroushtml配置或dangerouslysetinnerhtml(慎用)等方式实现html渲染。

MDX 里直接写 <div> 会被当成普通文本而不是 HTML
<p>MDX 默认把未包裹的 HTML 标签当作文本节点处理,即使你写了 <code><button>Click</button>,它也不会渲染成可点击按钮,而是原样输出字符串。这是因为 MDX 的解析器(如 @mdx-js/mdx)默认禁用原生 HTML,防止 XSS 和保持内容安全。
- 只有显式启用
allowDangerousHtml: true(v2+)或 skipExport: true(旧版)才可能生效,但不推荐
- 更稳妥的做法是用 React 组件封装 HTML 行为,比如写一个
<htmlblock></htmlblock> 组件来包裹
- 如果你只是想加个样式容器,
<div classname="note">... 这类写法在 Next.js / Vite + MDX 环境中通常会报错,因为 className 不被识别 —— 得改用 <code>class 属性(注意不是驼峰)
用 export const components 注册自定义 HTML 包装组件
这是最可控、也最符合 MDX 设计意图的方式:把 HTML 片段包装成 React 组件,在 MDX 文件顶部导出并注入渲染器。
- 在 MDX 文件开头写:
export const components = {
html: ({ children, ...props }) => <div>{children}</div>
}
- 然后在正文中使用:
{`⚠️ 注意事项`}
- 好处是支持 props 透传、能结合 Tailwind 或 CSS Modules、不会触发安全警告
- 缺点是不能直接写
<script></script> 或内联事件(如 onclick),React 会忽略它们
dangerouslySetInnerHTML 能跑但不该乱用
真要执行含 JS 的 HTML 片段(比如嵌入统计代码、第三方 widget),只能靠 React 的 dangerouslySetInnerHTML,但它必须出现在自定义组件内部,不能裸写在 MDX 正文里。
- 新建一个
RawHtml.mdx 组件,里面写:export default function RawHtml({ html }) {
return <div dangerouslysetinnerhtml="{{" __html: html></div>;
}
- 在目标 MDX 文件中导入并使用:
import RawHtml from './RawHtml.mdx'
<rawhtml html="{`<button" onclick="alert(1)">test`} /></rawhtml>
- 注意:
html 字符串必须是服务端可信来源,用户输入的内容绝对不能直接传入
- Next.js 13+ App Router 下,这个组件必须标记为
"use client",否则会报 hydration error
静态 HTML 导出时,<!--html--> 注释无效
有人试过用 HTML 注释绕过解析,比如写 <!--html--><iframe src="..."></iframe>,但这只是注释掉内容,根本不会渲染。MDX 不识别这种伪指令。
- MDX v2 没有类似 Jekyll 的
{% raw %} 语法,别浪费时间找“开关”
- 如果目标是生成静态站点(如 Docusaurus、Hugo + MDX),HTML 片段必须提前转成组件或通过插件预处理
- VitePress 用户要注意:它的 MDX 支持有限,
dangerouslySetInnerHTML 在某些版本中会触发 warning,得配合 vite-plugin-mdx 配置白名单
事情说清了就结束。真正麻烦的从来不是“怎么塞进 HTML”,而是“谁来管它的生命周期、样式隔离和 SSR 兼容性”。
allowDangerousHtml: true(v2+)或 skipExport: true(旧版)才可能生效,但不推荐<htmlblock></htmlblock> 组件来包裹<div classname="note">... 这类写法在 Next.js / Vite + MDX 环境中通常会报错,因为 className 不被识别 —— 得改用 <code>class 属性(注意不是驼峰)
用 export const components 注册自定义 HTML 包装组件
这是最可控、也最符合 MDX 设计意图的方式:把 HTML 片段包装成 React 组件,在 MDX 文件顶部导出并注入渲染器。
- 在 MDX 文件开头写:
export const components = { html: ({ children, ...props }) => <div>{children}</div> } - 然后在正文中使用:
{`⚠️ 注意事项`} - 好处是支持 props 透传、能结合 Tailwind 或 CSS Modules、不会触发安全警告
- 缺点是不能直接写
<script></script>或内联事件(如onclick),React 会忽略它们
dangerouslySetInnerHTML 能跑但不该乱用
真要执行含 JS 的 HTML 片段(比如嵌入统计代码、第三方 widget),只能靠 React 的 dangerouslySetInnerHTML,但它必须出现在自定义组件内部,不能裸写在 MDX 正文里。
- 新建一个
RawHtml.mdx组件,里面写:export default function RawHtml({ html }) { return <div dangerouslysetinnerhtml="{{" __html: html></div>; } - 在目标 MDX 文件中导入并使用:
import RawHtml from './RawHtml.mdx' <rawhtml html="{`<button" onclick="alert(1)">test`} /></rawhtml> - 注意:
html字符串必须是服务端可信来源,用户输入的内容绝对不能直接传入 - Next.js 13+ App Router 下,这个组件必须标记为
"use client",否则会报 hydration error
静态 HTML 导出时,<!--html--> 注释无效
有人试过用 HTML 注释绕过解析,比如写 <!--html--><iframe src="..."></iframe>,但这只是注释掉内容,根本不会渲染。MDX 不识别这种伪指令。
- MDX v2 没有类似 Jekyll 的
{% raw %}语法,别浪费时间找“开关” - 如果目标是生成静态站点(如 Docusaurus、Hugo + MDX),HTML 片段必须提前转成组件或通过插件预处理
- VitePress 用户要注意:它的 MDX 支持有限,
dangerouslySetInnerHTML在某些版本中会触发 warning,得配合vite-plugin-mdx配置白名单











