MDX 是什么
Markdown + JSX = MDX——能在 markdown 文件里直接嵌 React 组件。
# My Post
This is regular markdown.
import Chart from '@/components/Chart';
<Chart data={[1, 2, 3]} />
More markdown after the component.
装
npm install @next/mdx @mdx-js/loader @mdx-js/react
配置 next.config.ts
import createMDX from '@next/mdx';
const withMDX = createMDX({
extension: /\.mdx?$/,
});
const nextConfig = {
pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
};
export default withMDX(nextConfig);
在 app/ 里直接放 .mdx
src/app/
├── blog/
│ ├── page.tsx ← 博客列表
│ └── hello/
│ └── page.mdx ← /blog/hello
src/app/blog/hello/page.mdx:
# Hello World
Welcome to my first post.
## Code
Here's some code:
```js
console.log('hi');
访问 `/blog/hello` 看到渲染好的 markdown。
## 带 frontmatter(推荐)
但 Next.js 原生 MDX 没自带 frontmatter——用 `gray-matter` + 自定义加载:
```bash
npm install gray-matter
或更简单:把 metadata 直接 export:
export const metadata = {
title: 'Hello',
date: '2026-05-09',
description: 'My first post',
};
# Hello World
Body...
列表所有博客(生成博客索引)
// src/app/blog/page.tsx
import { readdirSync } from 'fs';
import { join } from 'path';
import Link from 'next/link';
async function getPosts() {
const dir = join(process.cwd(), 'src/app/blog');
const entries = readdirSync(dir, { withFileTypes: true });
const slugs = entries.filter(e => e.isDirectory()).map(e => e.name);
const posts = await Promise.all(slugs.map(async slug => {
const { metadata } = await import(`./${slug}/page.mdx`);
return { slug, ...metadata };
}));
return posts.sort((a, b) => b.date.localeCompare(a.date));
}
export default async function BlogIndex() {
const posts = await getPosts();
return (
<ul className="space-y-4">
{posts.map(p => (
<li key={p.slug}>
<Link href={`/blog/${p.slug}`} className="text-xl hover:underline">
{p.title}
</Link>
<p className="text-gray-500 text-sm">{p.date}</p>
</li>
))}
</ul>
);
}
自定义组件(如更漂亮的代码块)
src/mdx-components.tsx:
import type { MDXComponents } from 'mdx/types';
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
h1: ({ children }) => (
<h1 className="text-4xl font-bold mt-8 mb-4">{children}</h1>
),
h2: ({ children }) => (
<h2 className="text-2xl font-bold mt-6 mb-3">{children}</h2>
),
a: ({ href, children }) => (
<a href={href} className="text-blue-600 hover:underline">{children}</a>
),
code: ({ children }) => (
<code className="bg-gray-100 px-1 py-0.5 rounded">{children}</code>
),
...components,
};
}
所有 .mdx 文件里的元素都用这套样式。
代码块语法高亮
npm install rehype-pretty-code shiki
next.config.ts:
import createMDX from '@next/mdx';
import rehypePrettyCode from 'rehype-pretty-code';
const withMDX = createMDX({
extension: /\.mdx?$/,
options: {
rehypePlugins: [
[rehypePrettyCode, { theme: 'github-dark' }],
],
},
});
之后 .mdx 里 ```js 代码块会有漂亮高亮。
在 MDX 里嵌组件
import { LineChart } from '@/components/LineChart';
import { Callout } from '@/components/Callout';
# My Post
<Callout type="warning">
注意:此章节假设你已读过前面。
</Callout>
数据可视化:
<LineChart data={[10, 20, 30, 25, 40]} />
继续 markdown 内容...
替代方案:fumadocs / nextra
如果只是想要"开箱即用的博客 / 文档站"——这两个框架基于 Next.js 内置了一切(侧栏 / 搜索 / TOC):
npx create-fumadocs-app
坑
- MDX 里 import 路径用 alias(
@/)—— 不要相对路径 - frontmatter 不是原生支持——要用 export const metadata
- 代码块高亮构建期跑——大量代码块构建慢,考虑用
bright/@shikijs/transformers - MDX 里不能有
<>的字面字符——要转义或包代码块
下一篇:Server Actions 提交表单。