The Own Lab The Own Lab

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,支援標準的 importexport

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 只能包含表達式(有回傳值),不能包含語句ifforlet)。需要條件邏輯時用三元運算子或 &&

<!-- ❌ 語句不能用在 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&lt;string&gt; 非常實用。

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)

登入後即可留言