这篇指南说明如何在 Elaine Blog 中编写 MDX 文章。完成本文示例后,你可以在 Markdown 正文中使用 JavaScript 数据、表达式和 Astro 组件,并能判断哪些内容需要 MDX。
文中的每个主要示例都包含“代码”和“实际渲染结果”。你现在阅读的页面本身也是一篇 MDX 文章。
目录
- MDX 适合解决什么问题
- 创建第一篇 MDX 文章
- 使用变量和表达式
- 循环渲染数据
- 导入 Astro 组件
- 传递 Props 和插槽内容
- 在组件中渲染 Markdown
- 添加 HTML 和样式
- 使用图片
- 理解客户端交互
- 排查常见问题
- 检查并发布
MDX 适合解决什么问题
当普通 Markdown 不能表达所需的页面结构时,使用 MDX。MDX 保留标题、列表、引用和代码块等 Markdown 语法,同时允许你导入组件和运行构建期 JavaScript 表达式。
| 需求 | 推荐格式 |
|---|---|
| 普通技术文章 | .md |
| 在文章中渲染 Astro 组件 | .mdx |
| 根据数组批量生成内容 | .mdx |
| 使用 Props 和插槽组合内容 | .mdx |
| 只需要代码高亮 | .md |
优先使用 .md。只有文章确实需要组件或表达式时,才切换为 .mdx,这样内容更容易迁移和维护。
创建第一篇 MDX 文章
在 src/content/posts/ 下创建一个不以 _ 开头的 .mdx 文件:
src/content/posts/my-mdx-post.mdx
添加项目要求的 Frontmatter 和正文:
---
title: 我的第一篇 MDX 文章
pubDatetime: 2026-08-27T10:00:00+08:00
category: docs
description: 展示 Elaine Blog 中最小可用的 MDX 文章。
tags:
- MDX
---
## Hello MDX
这部分仍然使用普通 Markdown。
<p>这一行使用 JSX 风格的元素。</p>
启动开发服务器后,访问 /posts/my-mdx-post/ 查看文章:
pnpm run dev
使用变量和表达式
在 MDX 顶层通过 export const 声明数据,在正文中使用一对花括号读取数据。
代码
export const guideMeta = {
format: ".mdx",
framework: "Astro",
language: "zh-CN",
};
当前格式:{guideMeta.format}
构建框架:{guideMeta.framework}
实际渲染结果
读取 MDX 顶层变量
当前格式:.mdx
构建框架:Astro
内容语言:zh-CN
花括号中可以使用属性访问、模板字符串、三元表达式和函数调用。不要在花括号内放入带有副作用的浏览器代码,因为这些表达式主要在构建期间执行。
循环渲染数据
使用数组的 map() 方法生成重复内容。为每一项提供稳定的 key,便于后续将示例迁移到其他 JSX 框架。
代码
export const mdxFeatures = [
"在 Markdown 中使用 JavaScript 表达式",
"导入并组合 Astro 组件",
"通过 Props 和插槽组织内容",
];
<ul>
{mdxFeatures.map(feature => <li key={feature}>{feature}</li>)}
</ul>
实际渲染结果
- 在 Markdown 中使用 JavaScript 表达式
- 导入并组合 Astro 组件
- 通过 Props 和插槽组织内容
如果回调包含多行 JSX,请使用圆括号包裹返回内容:
{mdxFeatures.map(feature => (
<article key={feature}>
<strong>{feature}</strong>
</article>
))}
导入 Astro 组件
在 Frontmatter 结束后、正文开始前导入组件。项目已配置 @/ 路径别名,它指向 src/。
代码
import MdxDemoCard from "@/components/mdx/MdxDemoCard.astro";
<MdxDemoCard title="组件导入成功">
<p>这段内容由 MDX 传入 Astro 组件。</p>
</MdxDemoCard>
实际渲染结果
组件导入成功
这段内容由 MDX 传入 Astro 组件。
组件名必须以大写字母开头。小写名称会被 MDX 当作原生 HTML 元素处理。
传递 Props 和插槽内容
Props 用于传入结构化数据,组件标签之间的内容会进入 Astro 的默认插槽。
本文使用的 MdxDemoCard 接收以下 Props:
| Prop | 类型 | 是否必填 | 作用 |
|---|---|---|---|
title | string | 是 | Demo 标题 |
eyebrow | string | 否 | 顶部状态标签 |
items | string[] | 否 | 右侧能力列表 |
代码
<MdxDemoCard
title="Props 与插槽"
eyebrow="COMPONENT · LIVE"
items={mdxFeatures}
>
<p>
这段文字位于组件标签之间,因此会传给组件的默认插槽。
</p>
</MdxDemoCard>
实际渲染结果
Props 与插槽
这段文字位于组件标签之间,因此会传给组件的默认插槽。
- 在 Markdown 中使用 JavaScript 表达式
- 导入并组合 Astro 组件
- 通过 Props 和插槽组织内容
字符串 Prop 可以直接写入属性。数组、对象、数字、布尔值和表达式需要放入花括号:
<MdxDemoCard
title="字符串"
items={["数组第一项", "数组第二项"]}
/>
在组件中渲染 Markdown
如果组件提供默认插槽,可以把 Markdown 内容放在组件标签之间。开始标签、Markdown 和结束标签之间需要保留空行。
代码
<ResponsiveTable variant="striped-minimal">
| 能力 | Markdown | MDX |
| --- | --- | --- |
| 普通正文 | 支持 | 支持 |
| 导入组件 | 不支持 | 支持 |
| JavaScript 表达式 | 不支持 | 支持 |
</ResponsiveTable>
实际渲染结果
| 能力 | Markdown | MDX |
|---|---|---|
| 普通正文 | 支持 | 支持 |
| 导入组件 | 不支持 | 支持 |
| JavaScript 表达式 | 不支持 | 支持 |
如果删除组件标签和表格之间的空行,MDX 可能把表格当成普通文本或 JSX 子节点。遇到内容没有按预期渲染时,先检查空行。
添加 HTML 和样式
MDX 允许直接编写 HTML 风格的 JSX。属性名和结束标签需要符合 JSX 语法。
代码
<aside class="rounded-lg border border-dashed p-4">
<strong>提示:</strong>
<span>这是一段使用 HTML 结构编写的内容。</span>
</aside>
实际渲染结果
在当前 Astro 项目中可以使用 class。使用 Tailwind 类名时,请把完整类名直接写在源码中,避免通过字符串拼接动态生成类名,否则 Tailwind 可能无法识别它们。
JSX 中的 <img>、<br> 和 <input> 等空元素必须使用自闭合写法,例如 <br />。
使用图片
最直接的方式是使用 Markdown 图片语法或原生 <img />。为每张图片提供描述内容的替代文本。
Markdown 图片

JSX 图片
<img
src="/elaine-blog-og.png"
alt="Elaine Blog 分享图"
width="1200"
height="630"
/>
实际渲染结果
如果图片与文章放在同一目录,可以使用 ./assets/example.png 形式的相对路径。需要 Astro 图片优化时,导入 astro:assets 的 Image 组件和本地图片资源。
import { Image } from "astro:assets";
import cover from "./assets/cover.png";
<Image src={cover} alt="文章封面" />
理解客户端交互
MDX 中的 JavaScript 表达式默认在构建期间运行。导入 Astro 组件也不会自动向浏览器发送 JavaScript。
如果只需要计数器、展开详情或表单校验等基础交互,优先使用原生 HTML 或带 <script> 的 Astro 组件。下面的计数器由 MdxCounterDemo.astro 提供客户端脚本。
代码
import MdxCounterDemo from "@/components/mdx/MdxCounterDemo.astro";
<MdxCounterDemo initial={3} step={2} />
实际渲染结果
Astro 会打包组件中的 <script>,因此这个示例不需要 client:* 指令。组件脚本通过 astro:page-load 事件兼容项目的页面切换功能。
当前项目没有安装 React、Vue 或 Svelte 集成,因此不能直接导入这些框架的组件。
需要使用框架组件时,先安装对应的 Astro 集成,再根据组件类型添加 client:* 指令:
import Counter from "@/components/Counter.jsx";
<Counter client:visible initialValue={0} />
常用客户端指令:
| 指令 | 加载时机 | 适用场景 |
|---|---|---|
client:load | 页面加载后立即激活 | 首屏关键交互 |
client:idle | 浏览器空闲时激活 | 非关键交互 |
client:visible | 组件进入视口时激活 | 页面下方组件 |
client:media | 媒体条件匹配时激活 | 特定屏幕尺寸的组件 |
client:only | 只在客户端渲染 | 依赖浏览器 API 的组件 |
排查常见问题
| 问题 | 原因 | 处理方法 |
|---|---|---|
| 文章没有出现 | 文件名以 _ 开头或设置了 draft: true | 修改文件名并检查 Frontmatter |
| 组件显示成普通标签 | 组件名以小写字母开头 | 使用大写组件名 |
出现 Component is not defined | 没有导入组件或路径错误 | 检查顶部 import 和 @/ 路径 |
| 花括号语法报错 | 表达式不是有效 JavaScript | 单独运行或简化表达式 |
| 表格显示成纯文本 | JSX 标签与 Markdown 之间没有空行 | 在标签和表格之间加入空行 |
| 浏览器中没有交互 | 组件只进行了服务端渲染 | 使用 Astro 脚本或框架客户端指令 |
| 图片构建失败 | 相对路径无法解析 | 从当前文章位置重新计算路径 |
检查并发布
在 elaine-blog 项目目录中执行以下步骤:
-
运行开发服务器并检查所有实时 Demo:
pnpm run dev -
检查浅色和深色模式下的文字、边框和图片。
-
检查移动端布局,确认表格可以横向滚动。
-
运行生产构建:
pnpm run build -
修复构建输出中的 Frontmatter、导入路径或 JSX 错误。
下一步
- 阅读博客生成细则,了解文件命名、目录、草稿和发布时间规则。
- 复制本文中的最小示例,在
src/content/posts/下创建自己的.mdx文章。 - 查看
src/components/mdx/MdxDemoCard.astro,学习 Props、默认值、插槽和组件级样式。