MDX 實戰指南
在 Markdown 中使用 JSX 元件的語法規則、常見陷阱與最佳實踐
Overview##
MDX 讓你在 Markdown 中直接使用 JSX 元件。它不是 Markdown 的「擴充」,而是將 Markdown 和 JSX 合併成一種新的格式——.mdx 檔案會被編譯為 JavaScript module。
Markdown → CommonMark 語法,純文字可讀
GFM → + Table, Task List, Strikethrough...
MDX → + JSX, import/export, expression
MDX 的核心價值:
| 特性 | 說明 |
|---|---|
| JSX 元件 | 在文件中嵌入互動式 UI 元件 |
import / export | 匯入元件、匯出 metadata |
| Expression | 用 {} 嵌入 JavaScript 表達式 |
| Frontmatter | 透過 remark plugin 支援 YAML metadata |
Note
MDX 目前有兩個主要版本:MDX 1(已停止維護)和 MDX 2+(現行版本)。本文以 MDX 2+ 為基準,語法與 v1 有重大差異。
Syntax##
JSX 元件###
MDX 最核心的功能——在 Markdown 中直接使用元件:
import { Chart } from './components/Chart';
# Sales Report
以下是本季度的銷售數據:
<Chart data={salesData} type="bar" />
如圖所示,Q3 的成長最為顯著。
元件與 Markdown 可以自由交錯,但需要遵守一個關鍵規則:
Warning
JSX 標籤與 Markdown 內容之間必須有空行,否則 Markdown 語法不會被解析。
<!-- ❌ Markdown 語法不會被解析 -->
<div>
**This won't be bold**
</div>
<!-- ✅ 加上空行 -->
<div>
**This will be bold**
</div>
Import / Export###
MDX 檔案就是 JavaScript module,支援標準的 import 和 export:
import { Tabs, TabItem } from '@components/Tabs';
import { version } from '../package.json';
export const metadata = {
author: 'Brian',
tags: ['mdx', 'tutorial'],
};
# Getting Started (v{version})
<Tabs>
<TabItem label="npm">
```bash
npm install mdx
```
</TabItem>
<TabItem label="pnpm">
```bash
pnpm add mdx
```
</TabItem>
</Tabs>
| 語法 | 用途 |
|---|---|
import | 匯入元件、資料、樣式 |
export | 匯出 metadata、layout、常數 |
export default | 指定 layout wrapper 元件 |
Expression###
用 {} 嵌入 JavaScript 表達式:
export const frameworks = ['React', 'Vue', 'Svelte', 'Astro'];
# Supported Frameworks
目前支援 {frameworks.length} 個框架:
<ul>
{frameworks.map((f) => (
<li key={f}>{f}</li>
))}
</ul>
最後更新:{new Date().toLocaleDateString('zh-TW')}
Tip
Expression 只能包含表達式(有回傳值),不能包含語句(if、for、let)。需要條件邏輯時用三元運算子或 &&。
<!-- ❌ 語句不能用在 expression 中 -->
{if (condition) { return <A /> }}
<!-- ✅ 三元運算子 -->
{condition ? <A /> : <B />}
<!-- ✅ 短路求值 -->
{showWarning && <Warning />}
Common Pitfalls##
HTML 註解###
MDX 2+ 不支援 HTML 註解語法,這是從 MDX 1 遷移時最常遇到的問題:
<!-- ❌ 會導致編譯錯誤 -->
<!-- This is a comment -->
{/* ✅ 使用 JSX 註解 */}
{/* This is a comment */}
縮排的 JSX###
在清單或 blockquote 內使用 JSX 時,縮排會造成問題:
<!-- ❌ 被縮排的 JSX 可能被解析為 code block -->
- Item 1
<MyComponent />
<!-- ✅ 確保 JSX 不會被誤判為 code block -->
- Item 1
<MyComponent />
大括號跳脫###
MDX 中 { 和 } 是特殊字元,純文字中需要跳脫:
<!-- ❌ 會被解析為 expression -->
CSS 選擇器 .container { display: flex; }
<!-- ✅ 用反斜線跳脫 -->
CSS 選擇器 .container \{ display: flex; \}
<!-- ✅ 或放在 code block / inline code 中(自動跳脫) -->
CSS 選擇器 `.container { display: flex; }`
角括號###
泛型語法中的 < 會被解析為 JSX 標籤:
<!-- ❌ 被當成 JSX 標籤 -->
TypeScript 的 Array<string> 非常實用。
<!-- ✅ 用 inline code -->
TypeScript 的 `Array<string>` 非常實用。
<!-- ✅ 或用 HTML entity -->
TypeScript 的 Array<string> 非常實用。
MDX vs Markdown##
了解何時該用 MDX,何時純 Markdown 就夠了:
| 面向 | Markdown (.md) | MDX (.mdx) |
|---|---|---|
| 互動元件 | ❌ | ✅ |
| 語法複雜度 | 低 | 中 |
| 編譯需求 | 不需要 | 需要 bundler |
| 可攜性 | 所有平台 | 限 JS 生態系 |
| 學習曲線 | 低 | 需懂 JSX |
| 效能 | 靜態 HTML | 包含 JS bundle |
| 編輯器支援 | 完整 | 部分(持續改善中) |
Tip
選擇原則:純文件內容用 .md,需要互動或自訂元件時才用 .mdx。不要為了用 MDX 而用 MDX——每個 .mdx 檔案都會增加 JS bundle 大小。
Framework Integration##
MDX 需要搭配框架使用。以下是主流框架的整合方式:
Astro###
// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [mdx()],
});
Astro 的 MDX 預設為零 JS——元件在 build time 渲染為靜態 HTML,除非加上 client:* directive:
import Counter from '@components/Counter';
{/* 靜態渲染,不送 JS 到瀏覽器 */}
<Counter />
{/* 加上 client directive 才會 hydrate */}
<Counter client:visible />
Next.js###
// next.config.mjs
import createMDX from '@next/mdx';
const withMDX = createMDX({
extension: /\.mdx?$/,
});
export default withMDX({
pageExtensions: ['js', 'jsx', 'md', 'mdx'],
});
Vite###
// vite.config.js
import { defineConfig } from 'vite';
import mdx from '@mdx-js/rollup';
export default defineConfig({
plugins: [mdx()],
});
Best Practices##
元件設計原則###
<!-- ❌ 在 MDX 中寫複雜邏輯 -->
export const data = await fetch('/api/data').then(r => r.json());
<table>
{data.map(row => (
<tr>
{Object.values(row).map(cell => <td>{cell}</td>)}
</tr>
))}
</table>
<!-- ✅ 邏輯封裝在元件內,MDX 保持簡潔 -->
import { DataTable } from '@components/DataTable';
<DataTable endpoint="/api/data" />
檔案組織###
src/content/docs/
├── guide/
│ ├── 01-getting-started.mdx ← 需要互動元件
│ └── 02-configuration.md ← 純文字說明
└── components/
├── CodePlayground.astro ← 可重用的 MDX 元件
└── ArchitectureDiagram.astro
Note
將元件集中管理在 components/ 目錄,而非散落在各個 MDX 檔案中。MDX 檔案應專注於內容,元件負責呈現邏輯。
Frontmatter 搭配###
MDX 本身不支援 frontmatter,需要透過 remark plugin(如 remark-frontmatter)。大多數框架已內建支援:
---
title: My Article
description: An example article
---
# {frontmatter.title}
存取方式因框架而異——Astro 用 `frontmatter.*`,Next.js 需要 `export` metadata。
Summary##
- MDX = Markdown + JSX,檔案會被編譯為 JavaScript module
- JSX 與 Markdown 交錯時必須用空行隔開
- 使用
{/* */}註解,不用<!-- --> {}和<>在 MDX 中是特殊字元,純文字中需要跳脫或用 inline code- Expression 只能用表達式,不能用語句
- 複雜邏輯封裝在元件內,MDX 保持內容導向
- 不需要互動元件的文件,用
.md就好
留言 (0)
登入後即可留言