MDX
Markdown 是一种轻量级标记语言,用于格式化文本。它允许你使用纯文本语法编写内容,并将其转换为结构有效的 HTML。它通常用于网站和博客的内容编写。
你可以这样编写...
I **love** using [Next.js](https://nextjs.org/)
输出结果:
<p>I <strong>love</strong> using <a href="https://nextjs.org/">Next.js</a></p>
MDX 是 markdown 的超集,允许你直接在 markdown 文件中编写 JSX。这是一种强大的方式,可以为内容添加动态交互性并嵌入 React 组件。
Next.js 既支持应用程序内部的本地 MDX 内容,也支持在服务器上动态获取的远程 MDX 文件。Next.js 插件负责将 markdown 和 React 组件转换为 HTML,包括在服务端组件(App Router 中的默认组件)中的使用支持。
@next/mdx
@next/mdx
包用于配置 Next.js 以处理 markdown 和 MDX。它从本地文件获取数据,允许你直接在 /pages
或 /app
目录中创建 .mdx
扩展名的页面。
让我们逐步了解如何配置和使用 Next.js 中的 MDX。
开始使用
安装渲染 MDX 所需的包:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
在应用程序的根目录(app/
或 src/
的父文件夹)创建一个 mdx-components.tsx
文件:
须知:
mdx-components.tsx
是使用 App Router 的 MDX 所必需的,没有它将无法工作。
import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
}
}
export function useMDXComponents(components) {
return {
...components,
}
}
更新项目根目录下的 next.config.js
文件以配置使用 MDX:
const withMDX = require('@next/mdx')()
/** @type {import('next').NextConfig} */
const nextConfig = {
// 配置 `pageExtensions` 以包含 MDX 文件
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
// 可选,添加其他 Next.js 配置
}
module.exports = withMDX(nextConfig)
然后在 /app
目录中创建一个新的 MDX 页面:
your-project
├── app
│ └── my-mdx-page
│ └── page.mdx
└── package.json
现在你可以在 MDX 页面中使用 markdown 并直接导入 React 组件:
import { MyComponent } from 'my-components'
# 欢迎来到我的 MDX 页面!
这是一些**加粗**和*斜体*文本。
这是一个 markdown 列表:
- 一
- 二
- 三
看看我的 React 组件:
<MyComponent />
访问 /my-mdx-page
路由应显示渲染后的 MDX。
远程 MDX
如果你的 markdown 或 MDX 文件或内容位于其他地方,你可以在服务器上动态获取它。这对于存储在单独本地文件夹、CMS、数据库或其他任何地方的内容非常有用。
有两个流行的社区包用于获取 MDX 内容:
须知:请谨慎操作。MDX 会编译为 JavaScript 并在服务器上执行。你应仅从可信来源获取 MDX 内容,否则可能导致远程代码执行 (RCE)。
以下示例使用 next-mdx-remote
:
import { MDXRemote } from 'next-mdx-remote/rsc'
export default async function RemoteMdxPage() {
// MDX 文本 - 可以来自本地文件、数据库、CMS、fetch 请求等...
const res = await fetch('https://...')
const markdown = await res.text()
return <MDXRemote source={markdown} />
}
import { MDXRemote } from 'next-mdx-remote/rsc'
export default async function RemoteMdxPage() {
// MDX 文本 - 可以来自本地文件、数据库、CMS、fetch 请求等...
const res = await fetch('https://...')
const markdown = await res.text()
return <MDXRemote source={markdown} />
}
访问 /my-mdx-page-remote
路由应显示渲染后的 MDX。
布局
要在 MDX 页面之间共享布局,你可以使用 App Router 的内置布局支持。
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// 在此创建任何共享布局或样式
return <div style={{ color: 'blue' }}>{children}</div>
}
export default function MdxLayout({ children }) {
// 在此创建任何共享布局或样式
return <div style={{ color: 'blue' }}>{children}</div>
}
Remark 和 Rehype 插件
你可以选择性地提供 remark
和 rehype
插件来转换 MDX 内容。
例如,你可以使用 remark-gfm
来支持 GitHub 风格的 Markdown。
由于 remark
和 rehype
生态系统仅支持 ESM,你需要使用 next.config.mjs
作为配置文件。
import remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// 配置 `pageExtensions` 以包含 MDX 文件
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
// 可选,添加其他 Next.js 配置
}
const withMDX = createMDX({
// 在此添加所需的 markdown 插件
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
})
// 将 MDX 配置与 Next.js 配置合并
export default withMDX(nextConfig)
Frontmatter
Frontmatter 是一种类似 YAML 的键/值对,可用于存储页面的数据。@next/mdx
默认不支持 frontmatter,但有许多解决方案可以为你的 MDX 内容添加 frontmatter,例如:
要使用 @next/mdx
访问页面元数据,你可以从 .mdx
文件中导出一个 meta 对象:
export const meta = {
author: 'John Doe',
}
# 我的 MDX 页面
自定义元素
使用 markdown 的一个优点是它映射到原生 HTML
元素,使得编写快速且直观:
这是一个 markdown 列表:
- 一
- 二
- 三
上述内容生成以下 HTML
:
<p>这是一个 markdown 列表:</p>
<ul>
<li>一</li>
<li>二</li>
<li>三</li>
</ul>
当你想要为自己的元素添加样式以赋予网站或应用程序独特的外观时,可以传递短代码。这些是你自己的自定义组件,映射到 HTML
元素。
为此,打开应用程序根目录下的 mdx-components.tsx
文件并添加自定义元素:
import type { MDXComponents } from 'mdx/types'
import Image from 'next/image'
// 此文件允许你提供自定义 React 组件
// 用于 MDX 文件。你可以导入和使用任何
// React 组件,包括内联样式、
// 来自其他库的组件等。
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// 允许自定义内置组件,例如添加样式。
h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...props}
/>
),
...components,
}
}
import Image from 'next/image'
// 此文件允许你提供自定义 React 组件
// 用于 MDX 文件。你可以导入和使用任何
// React 组件,包括内联样式、
// 来自其他库的组件等。
export function useMDXComponents(components) {
return {
// 允许自定义内置组件,例如添加样式。
h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...props}
/>
),
...components,
}
}
深入探讨:如何将 markdown 转换为 HTML?
React 本身不理解 markdown。markdown 纯文本需要首先转换为 HTML。这可以通过 remark
和 rehype
实现。
remark
是一个围绕 markdown 的工具生态系统。rehype
则是针对 HTML 的类似工具。例如,以下代码片段将 markdown 转换为 HTML:
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
main()
async function main() {
const file = await unified()
.use(remarkParse) // 转换为 markdown AST
.use(remarkRehype) // 转换为 HTML AST
.use(rehypeSanitize) // 清理 HTML 输入
.use(rehypeStringify) // 将 AST 转换为序列化的 HTML
.process('Hello, Next.js!')
console.log(String(file)) // <p>Hello, Next.js!</p>
}
remark
和 rehype
生态系统包含用于语法高亮、链接标题、生成目录等的插件。
当使用上述 @next/mdx
时,你不需要直接使用 remark
或 rehype
,因为它已经为你处理了。我们在此描述它是为了更深入地理解 @next/mdx
包在底层的工作方式。
使用基于 Rust 的 MDX 编译器(实验性)
Next.js 支持一个新的基于 Rust 的 MDX 编译器。此编译器仍处于实验阶段,不建议在生产环境中使用。要使用新编译器,你需要在将 next.config.js
传递给 withMDX
时进行配置:
module.exports = withMDX({
experimental: {
mdxRs: true,
},
})