首页 / Astro 教程 / MDX:嵌入组件与表达式

Astro 教程

MDX:嵌入组件与表达式

本教程共 56 篇 · 第 18 篇 · 更新于 2026-08-07 · 约 9 分钟阅读

AstroAstro 教程MDX@astrojs/mdx集成Astro 组件JSX 表达式插槽

本节目标:学会用 MDX 让 Markdown 文章里能写变量、写表达式、直接放组件,让内容页不再只是纯文字。

上一章讲的 Markdown,擅长写文字,但有个局限:它只能写内容,不能写逻辑,也不能直接塞组件进去。MDX 就是来补这个短板的。

一句话概括:MDX 让你能在 Markdown 里写 JSX。JSX 是给界面写结构的一种语法,你可以把它理解成「能在文档里直接写组件、写变量」的能力。于是你的文章既能有 Markdown 的清爽,又能有组件的灵活。

先装上 MDX 集成

MDX 不是 Astro 默认开启的,得装一个官方「集成」(integration,就是给 Astro 加功能的插件包)。装它用一行命令:

npx astro add mdx

这个命令会自动把 @astrojs/mdx 装好,并写进配置文件。如果你更喜欢手动装,也行:

npm install @astrojs/mdx

装完再去 astro.config.mjs 里登记一下:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';

export default defineConfig({
  integrations: [mdx()],
});

记牢一件事:.mdx 文件用的是 MDX 语法,不是 Astro 那种像 HTML 的写法。在 .mdx 里写标签、写表达式,遵循的是 JSX 规矩。

Tip

用 VS Code 写 MDX,建议装官方的 MDX 扩展,写起来有高亮和提示,不容易写错。

在 MDX 里导出变量

MDX 支持用 export 语句定义变量,这个变量可以在文件里直接用,也能被导入它的组件拿到。

export const title = '我的第一篇 MDX 帖子'

# {title}

上面 {title} 就是 JSX 表达式的写法,花括号里放变量名,渲染时换成它的值。于是标题会显示成「我的第一篇 MDX 帖子」。

别的组件导入这个文件时,也能用这些导出的变量:

---
const matches = import.meta.glob('../posts/*.mdx', { eager: true });
const posts = Object.values(matches);
---

{posts.map(post => <p>{post.title}</p>)}

这里 post.title 拿到的,就是 MDX 里 export const title 的值。

用 frontmatter 当变量

MDX 集成默认也支持 frontmatter,写法和普通 Markdown 一样。这些属性在模板里通过 frontmatter.xxx 访问。

---
title: '我的第一篇 MDX 帖子'
author: 'Houston'
---

# {frontmatter.title}

作者:{frontmatter.author}

这个能力和前面「导出变量」不冲突,你按习惯选一种就行。很多人的习惯是:文章元信息(标题、作者)放 frontmatter,需要动态计算的才用 export

在 MDX 里用组件

装了 MDX 集成后,你可以把 Astro 组件,甚至 React、Vue 这类 UI 框架组件,直接 import 进 .mdx 使用,跟在 .astro 文件里用组件没两样。

---
title: 我的第一篇帖子
---

import ReactCounter from '../components/ReactCounter.jsx';

我刚开了个 Astro 博客!

下面这个计数器组件,在 MDX 里也能跑:

<ReactCounter client:load />

注意那行 client:load。它是「客户端指令」(client directive),告诉 Astro:这个 UI 框架组件需要在浏览器里「水合」(hydration,让静态组件变成可交互)后用。client:load 表示页面一加载就水合。忘了写指令,组件只会渲染个静态样子,点了没反应。

把 HTML 元素换成自定义组件

MDX 有个很妙的能力:你可以让某个 HTML 标签,自动用上你自己的组件。比如你想给所有引用块 <blockquote> 加特殊样式,不用每处手写组件,定义一次映射就好。

先写一个 Astro 组件:

---
const props = Astro.props;
---

<blockquote {...props} class="bg-blue-50 p-4">
  <span class="text-4xl text-blue-600 mb-2">“</span>
  <slot />  <!-- 引用内容从这里进来 -->
</blockquote>

再在 MDX 里导入它,并导出一个 components 映射:

import Blockquote from '../components/Blockquote.astro';

export const components = { blockquote: Blockquote }

> 这句话会自动用上自定义的 Blockquote 样式

这样写普通 Markdown 的 > 引用语法,实际渲染的却是你的 Blockquote 组件。MDX 支持把 h1h2blockquote 等一大堆 HTML 元素都替换掉,想知道全清单可以去 MDX 官网查。

给导入的 MDX 传组件

当你用 <Content /> 渲染一篇导入的 MDX 时,可以通过 components 这个属性,把自定义组件传进去。

---
import { Content, components } from '../content.mdx';
import Heading from '../Heading.astro';
---

<!-- 给 # 语法换上自定义 h1,同时带上 MDX 文件里自己定义的组件 -->
<Content components={{ ...components, h1: Heading }} />

如果 MDX 是「内容集合」里的一项,渲染方式类似,只是要先从 astro:content<Content />

---
import { getEntry, render } from 'astro:content';
import CustomHeading from '../../components/CustomHeading.astro';

const entry = await getEntry('blog', 'post-1');
const { Content } = await render(entry);
---

<Content components={{ h1: CustomHeading }} />

components 对象把 HTML 元素名(如 h1)映射到你的组件。展开运算符 ...components 是把 MDX 文件自身导出的组件也一并带进来,省得漏掉。

MDX 和 Markdown 配置的关系

MDX 集成默认会继承你 markdown 里的配置,比如语法高亮、处理器设置。也就是说你给 Markdown 配的主题,MDX 一般也跟着用。

想单独给 .mdx 文件配不同的选项,在 mdx() 里写就行:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';

export default defineConfig({
  integrations: [
    mdx({
      syntaxHighlight: 'shiki',
      shikiConfig: { theme: 'dracula' },
    }),
  ],
});

如果哪天你想让 MDX 完全不继承 Markdown 的配置,把 extendMarkdownConfig 设成 false 即可。

Note

关于语法高亮(Shiki、Prism)的细节,下一章专门讲。这里只要知道 MDX 能沿用同一套高亮配置就好。

MDX 文件也能直接当页面

和 Markdown 一样,把 .mdx 文件放进 src/pages/ 目录,Astro 会自动把它变成一个网页。文件 src/pages/posts/hello.mdx 对应的网址就是 /posts/hello/

---
title: 我的 MDX 页面
---

import { Counter } from '../components/Counter.jsx';

# 欢迎

下面这个组件直接长在页面里:

<Counter client:load />

这种方式适合「内容里偶尔要嵌点交互」的单个页面。它和「放进内容集合统一管理」是两条路,按你内容规模选。

把 MDX 放进内容集合

如果你有一批 MDX 文章想集中管理,在内容集合的加载器里把 .mdx 也纳进来就好。glob 的匹配规则同时写 mdmdx

// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
  }),
});

export const collections = { blog };

这样 Markdown 和 MDX 混在同一个集合里也没问题。渲染时一律用 render()<Content />,上一章(第 19 章)讲的高亮配置对 MDX 同样生效。

Note

MDX 里如果用了客户端指令(如 client:load),那个组件在集合页面里依然会正常水合。内容集合不改变 MDX 的交互能力。

什么时候用 MDX,什么时候用纯 Markdown

一句话:纯文字内容用 Markdown,要嵌交互或动态逻辑才上 MDX。

Markdown 更轻、渲染更稳、写起来没心智负担。一篇普通博客、一份文档,Markdown 足够。只有当你确实需要在文章里放组件、写表达式、用变量时,MDX 的价值才显出来。

别为了「看起来高级」就全站用 MDX。文件越多、越杂,构建越慢,排查问题也越麻烦。按需使用最踏实。

Tip

拿不准时,默认写 .md。真碰到「这段内容想变成组件」的需求,再改成 .mdx,成本很低。

小结

MDX 的本质,是给 Markdown 加了「写变量、写表达式、放组件」的能力。装上 @astrojs/mdx 集成就能用;export 和 frontmatter 都能当变量;组件直接 import 进来用;用 components 映射还能把普通 HTML 标签换成你的组件。

MDX 文件既能直接放 pages/ 当页面,也能进内容集合统一管。它特别适合写「半文章半应用」的内容页,比如带可交互 Demo 的教程。下一章先聊语法高亮,第 20、21 章再回到内容集合。