跳到正文
Elaine Blog
返回

MDX 详细使用指南

文档编写

这篇指南说明如何在 Elaine Blog 中编写 MDX 文章。完成本文示例后,你可以在 Markdown 正文中使用 JavaScript 数据、表达式和 Astro 组件,并能判断哪些内容需要 MDX。

文中的每个主要示例都包含“代码”和“实际渲染结果”。你现在阅读的页面本身也是一篇 MDX 文章。

目录

MDX 适合解决什么问题

当普通 Markdown 不能表达所需的页面结构时,使用 MDX。MDX 保留标题、列表、引用和代码块等 Markdown 语法,同时允许你导入组件和运行构建期 JavaScript 表达式。

需求推荐格式
普通技术文章.md
在文章中渲染 Astro 组件.mdx
根据数组批量生成内容.mdx
使用 Props 和插槽组合内容.mdx
只需要代码高亮.md
Tip

优先使用 .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}

实际渲染结果

EXPRESSION · LIVE

读取 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>

实际渲染结果

如果回调包含多行 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 · LIVE

组件导入成功

这段内容由 MDX 传入 Astro 组件。

组件名必须以大写字母开头。小写名称会被 MDX 当作原生 HTML 元素处理。

传递 Props 和插槽内容

Props 用于传入结构化数据,组件标签之间的内容会进入 Astro 的默认插槽。

本文使用的 MdxDemoCard 接收以下 Props:

Prop类型是否必填作用
titlestringDemo 标题
eyebrowstring顶部状态标签
itemsstring[]右侧能力列表

代码

<MdxDemoCard
  title="Props 与插槽"
  eyebrow="COMPONENT · LIVE"
  items={mdxFeatures}
>
  <p>
    这段文字位于组件标签之间,因此会传给组件的默认插槽。
  </p>
</MdxDemoCard>

实际渲染结果

COMPONENT · LIVE

Props 与插槽

这段文字位于组件标签之间,因此会传给组件的默认插槽。

  • 在 Markdown 中使用 JavaScript 表达式
  • 导入并组合 Astro 组件
  • 通过 Props 和插槽组织内容

字符串 Prop 可以直接写入属性。数组、对象、数字、布尔值和表达式需要放入花括号:

<MdxDemoCard
  title="字符串"
  items={["数组第一项", "数组第二项"]}
/>

在组件中渲染 Markdown

如果组件提供默认插槽,可以把 Markdown 内容放在组件标签之间。开始标签、Markdown 和结束标签之间需要保留空行。

代码

<ResponsiveTable variant="striped-minimal">

| 能力 | Markdown | MDX |
| --- | --- | --- |
| 普通正文 | 支持 | 支持 |
| 导入组件 | 不支持 | 支持 |
| JavaScript 表达式 | 不支持 | 支持 |

</ResponsiveTable>

实际渲染结果

能力MarkdownMDX
普通正文支持支持
导入组件不支持支持
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 可能无法识别它们。

Warning

JSX 中的 <img><br><input> 等空元素必须使用自闭合写法,例如 <br />

使用图片

最直接的方式是使用 Markdown 图片语法或原生 <img />。为每张图片提供描述内容的替代文本。

Markdown 图片

![Elaine Blog 分享图](/elaine-blog-og.png)

JSX 图片

<img
  src="/elaine-blog-og.png"
  alt="Elaine Blog 分享图"
  width="1200"
  height="630"
/>

实际渲染结果

Elaine Blog 分享图

如果图片与文章放在同一目录,可以使用 ./assets/example.png 形式的相对路径。需要 Astro 图片优化时,导入 astro:assetsImage 组件和本地图片资源。

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} />

实际渲染结果

当前计数3

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 项目目录中执行以下步骤:

  1. 运行开发服务器并检查所有实时 Demo:

    pnpm run dev
  2. 检查浅色和深色模式下的文字、边框和图片。

  3. 检查移动端布局,确认表格可以横向滚动。

  4. 运行生产构建:

    pnpm run build
  5. 修复构建输出中的 Frontmatter、导入路径或 JSX 错误。

下一步


分享这篇文章:

上一篇
博客生成细则
下一篇
Agent Evaluation 零基础路线:先定义“好”,再谈优化