喂!我是 Wei

Front-End Engineer

Be a Problem Solver.

⌘K

導覽

所有文章緣起互動小功能

文章分類

目錄
什麼是 MDX?為什麼用 MDX?加入 MDX 到 Next.js 專案1. 安裝套件2. 建立文章資料夾3. 建立文章讀取工具:lib/posts.ts4. 建立文章頁面:app/posts/[slug]/page.tsx管理文章:本地檔案為主文章的基本格式解析 frontmatter:gray-matter編譯 MDX:next-mdx-remote建立文章頁小結

相關文章

Next.js Blog 實作文章目錄(TOC)

2026年3月7日

Next.js Blog 加入程式碼高亮(Shiki + rehype-pretty-code)

2026年3月6日

我是如何用 Next.js 打造這個技術 Blog

2026年3月4日

最新文章
全部 →
前端 CI/CD 與正式環境除錯:從 Pull Request 到事故排查
2026-06-24
即時資料怎麼選?Polling、SSE、WebSocket 比較
2026-06-23
前端系統設計:如何拆元件、資料流與大型專案架構?
2026-06-22
無障礙不是加 ARIA:語意化 HTML、鍵盤操作與焦點管理
2026-06-21
CSS 與 RWD 面試整理:Flexbox、Grid、定位與層疊脈絡
2026-06-19
← 返回文章列表

在 Next.js 使用 MDX 撰寫技術文章

2026年3月6日·約 6 分鐘閱讀·
Next.jsMDXBlog

在建立技術 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-headings

2. 建立文章資料夾

在專案根目錄建立 content/posts/,之後每篇文章就放在這裡:

blog/
├── content/
│   └── posts/
│       └── my-first-post.mdx   ← 文章放這裡
├── app/
├── lib/
└── ...

3. 建立文章讀取工具:lib/posts.ts

這個檔案負責讀取 .mdx 檔案、解析 frontmatter、編譯 MDX:

lib/posts.ts
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

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-matter
import 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-headings
import { 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

基本流程:

  1. 根據 slug 找到對應的 .mdx 檔案
  2. 讀取檔案內容
  3. 用 gray-matter 解析 frontmatter
  4. 用 compileMDX 編譯 MDX
  5. 渲染文章內容

這樣每個 URL(例如 /posts/nextjs-mdx-blog)都會自動對應到一篇文章。


小結

如果你正在用 Next.js 建立自己的技術 Blog,MDX 是一個非常適合的選擇。它同時保有 Markdown 易寫的優點,也提供了 React 層級的擴充能力。

這個 Blog 目前也是用這樣的方式管理文章。下一篇會繼續介紹 Code Highlight 的設定。

分享:XLinkedIn
← 上一篇Next.js Blog 加入程式碼高亮(Shiki + rehype-pretty-code)
下一篇 →Next.js Blog 實作文章目錄(TOC)