Markdown 与 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
扩展名的页面。
让我们逐步了解如何配置和使用 MDX 与 Next.js。
开始使用
安装渲染 MDX 所需的包:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
更新项目根目录下的 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)
然后,在 /pages
目录中创建一个新的 MDX 页面:
your-project
├── pages
│ └── my-mdx-page.mdx
└── package.json
现在,您可以在 MDX 页面中使用 Markdown 并直接导入 React 组件:
import { MyComponent } from 'my-components'
# 欢迎来到我的 MDX 页面!
这是一些 **粗体** 和 _斜体_ 文本。
这是一个 Markdown 列表:
- 一
- 二
- 三
看看我的 React 组件:
<MyComponent />
导航到 /my-mdx-page
路由应显示您渲染的 MDX。
远程 MDX
如果您的 Markdown 或 MDX 文件或内容位于 其他地方,您可以在服务器上动态获取它。这对于存储在单独本地文件夹、CMS、数据库或其他任何地方的内容非常有用。一个流行的社区包是 next-mdx-remote
。
须知:请谨慎操作。MDX 编译为 JavaScript 并在服务器上执行。您应该只从受信任的来源获取 MDX 内容,否则可能导致远程代码执行 (RCE)。
以下示例使用 next-mdx-remote
:
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
interface Props {
mdxSource: MDXRemoteSerializeResult
}
export default function RemoteMdxPage({ mdxSource }: Props) {
return <MDXRemote {...mdxSource} />
}
export async function getStaticProps() {
// MDX 文本 - 可以来自本地文件、数据库、CMS、fetch 等...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize(mdxText)
return { props: { mdxSource } }
}
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
export default function RemoteMdxPage({ mdxSource }) {
return <MDXRemote {...mdxSource} />
}
export async function getStaticProps() {
// MDX 文本 - 可以来自本地文件、数据库、CMS、fetch 等...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize(mdxText)
return { props: { mdxSource } }
}
导航到 /my-mdx-page-remote
路由应显示您渲染的 MDX。
布局
要在 MDX 页面之间共享布局,创建一个布局组件:
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>
}
然后,将布局组件导入 MDX 页面,将 MDX 内容包裹在布局中,并导出它:
import MdxLayout from '../components/mdx-layout'
# 欢迎来到我的 MDX 页面!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}
Remark 和 Rehype 插件
您可以选择提供 remark
和 rehype
插件来转换 MDX 内容。
例如,您可以使用 remark-gfm
来支持 GitHub Flavored 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
文件中导出一个元数据对象:
export const metadata = {
author: 'John Doe',
}
# 我的 MDX 页面
自定义元素
使用 Markdown 的一个愉快之处在于它映射到原生 HTML
元素,使编写快速且直观:
这是一个 Markdown 列表:
- 一
- 二
- 三
以上内容生成以下 HTML
:
<p>这是一个 Markdown 列表:</p>
<ul>
<li>一</li>
<li>二</li>
<li>三</li>
</ul>
当您想为自己的元素添加样式以赋予网站或应用程序自定义感觉时,可以使用短代码。这些是您自己的自定义组件,映射到 HTML
元素。
为此,在应用程序的根目录(pages/
或 src/
的父文件夹)创建一个 mdx-components.tsx
文件并添加自定义元素:
import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } 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 as ImageProps)}
/>
),
...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,
},
})