在建立技術 Blog 的時候,文章內容通常不只是單純的文字,還會包含大量的程式碼片段、標題階層,甚至是一些客製化元件。
如果只是使用一般 Markdown,雖然可以完成基本文章撰寫,但在延伸性上比較有限。這時候,MDX 就是一個很適合的選擇。
MDX 可以把 Markdown 和 React Component 結合在一起,讓文章不只是靜態內容,也能保有更高的彈性。
什麼是 MDX?
MDX 是 Markdown 的延伸格式,最大的特色是可以在文章中直接使用 React Component。
# Hello MDX
這是一段普通段落。
<MyComponent />這代表文章除了可以寫標題、段落、清單、程式碼區塊之外,也能加入自訂元件。對技術 Blog 來說,這種彈性非常實用,尤其未來如果想加入提示框、警告區塊、卡片元件,都能輕鬆擴充。
為什麼用 MDX?
選擇 MDX 主要有幾個原因:
- 很適合放程式碼內容 — Markdown 本身就支援 code block,MDX 完整保留了這個優點
- 文章內容更容易擴充 — 想在文章中嵌入自訂元件(提示區塊、Demo、卡片)時,MDX 比純 Markdown 更好延伸
- 搭配 Next.js 很直覺 — 專案本身就是 Next.js,直接在專案內管理
.mdx檔案,結構清楚,也方便做後續的標籤、目錄功能
加入 MDX 到 Next.js 專案
1. 安裝套件
npm install next-mdx-remote gray-matter
npm install rehype-slug rehype-autolink-headings2. 建立文章資料夾
在專案根目錄建立 content/posts/,之後每篇文章就放在這裡:
blog/
├── content/
│ └── posts/
│ └── my-first-post.mdx ← 文章放這裡
├── app/
├── lib/
└── ...
3. 建立文章讀取工具:lib/posts.ts
這個檔案負責讀取 .mdx 檔案、解析 frontmatter、編譯 MDX:
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
import { compileMDX } from "next-mdx-remote/rsc";
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
const POSTS_DIR = path.join(process.cwd(), "content", "posts");
// 取得所有文章的 metadata(標題、日期、標籤...)
export function getAllPostMeta() {
const files = fs.readdirSync(POSTS_DIR).filter((f) => f.endsWith(".mdx"));
return files.map((filename) => {
const slug = filename.replace(/\.mdx$/, "");
const raw = fs.readFileSync(path.join(POSTS_DIR, filename), "utf8");
const { data } = matter(raw);
return { slug, ...data };
});
}
// 取得單篇文章的內容
export async function getPostBySlug(slug: string) {
const filePath = path.join(POSTS_DIR, `${slug}.mdx`);
const raw = fs.readFileSync(filePath, "utf8");
const { data, content } = matter(raw);
const { content: compiled } = await compileMDX({
source: content,
options: {
mdxOptions: {
rehypePlugins: [
rehypeSlug,
rehypeAutolinkHeadings,
],
},
},
});
return { meta: data, content: compiled };
}4. 建立文章頁面:app/posts/[slug]/page.tsx
import { getPostBySlug, getAllPostMeta } from "@/lib/posts";
// 告訴 Next.js 有哪些 slug 要靜態產生
export function generateStaticParams() {
return getAllPostMeta().map(({ slug }) => ({ slug }));
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const { meta, content } = await getPostBySlug(params.slug);
return (
<article>
<h1>{meta.title}</h1>
<p>{meta.date}</p>
<div>{content}</div>
</article>
);
}完成以上四步後,只要在 content/posts/ 放一個 .mdx 檔案,對應的頁面就會自動出現。
管理文章:本地檔案為主
這個 Blog 的文章放在本地資料夾中管理,不使用資料庫:
content/
└── posts/
├── 2026-03-04-nextjs-blog-architecture.mdx
└── 2026-03-05-nextjs-mdx-blog.mdx
每一篇文章就是一個 .mdx 檔案,好處是:
- 結構單純,不需要先接資料庫
- 寫作和維護都很直覺
- 很適合個人技術 Blog 或 side project
文章的基本格式
一篇 MDX 文章通常包含兩個部分:frontmatter 和文章內容。
---
title: "My First Post"
date: "2026-03-01"
summary: "這是一篇測試文章"
tags: ["Next.js", "MDX"]
---
# My First Post
Hello world.--- 區塊就是 frontmatter,用來放文章的基本資訊:
| 欄位 | 說明 |
|---|---|
title | 文章標題 |
date | 發布日期 |
summary | 摘要(用於列表頁預覽) |
tags | 分類標籤 |
解析 frontmatter:gray-matter
因為文章裡有 frontmatter,需要先把它解析出來,這裡使用 gray-matter:
npm install gray-matterimport matter from "gray-matter";
const { data, content } = matter(fileContent);
// data → frontmatter 物件(title、date、tags...)
// content → 文章本文(不含 frontmatter)這樣就可以把文章的標題、日期、摘要分別用在文章列表頁或文章頁面。
編譯 MDX:next-mdx-remote
解析完 frontmatter 之後,接下來要把 MDX 內容編譯成可以渲染的 React 元件。這裡使用 next-mdx-remote,它有專為 React Server Components 設計的版本:
npm install next-mdx-remote rehype-slug rehype-autolink-headingsimport { compileMDX } from "next-mdx-remote/rsc";
const { content } = await compileMDX({
source: mdxContent,
options: {
mdxOptions: {
rehypePlugins: [
rehypeSlug, // 自動幫標題產生 id
rehypeAutolinkHeadings, // 讓標題可以被點擊錨點
],
},
},
});compileMDX 在 Server Component 中執行,編譯結果不會打進 client bundle,對效能影響很小。
建立文章頁
在 Next.js App Router 中,可以建立一個動態路由來顯示文章:
app/
└── posts/
└── [slug]/
└── page.tsx
基本流程:
- 根據
slug找到對應的.mdx檔案 - 讀取檔案內容
- 用
gray-matter解析 frontmatter - 用
compileMDX編譯 MDX - 渲染文章內容
這樣每個 URL(例如 /posts/nextjs-mdx-blog)都會自動對應到一篇文章。
小結
如果你正在用 Next.js 建立自己的技術 Blog,MDX 是一個非常適合的選擇。它同時保有 Markdown 易寫的優點,也提供了 React 層級的擴充能力。
這個 Blog 目前也是用這樣的方式管理文章。下一篇會繼續介紹 Code Highlight 的設定。