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:

next.config.js
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 } }
}

导航到 /my-mdx-page-remote 路由应显示您渲染的 MDX。

布局

要在 MDX 页面之间共享布局,创建一个布局组件:

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // 在此创建任何共享布局或样式
  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 插件

您可以选择提供 remarkrehype 插件来转换 MDX 内容。

例如,您可以使用 remark-gfm 来支持 GitHub Flavored Markdown。

由于 remarkrehype 生态系统仅支持 ESM,您需要使用 next.config.mjs 作为配置文件。

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,
  }
}

深入探讨:如何将 Markdown 转换为 HTML?

React 本身不理解 Markdown。Markdown 纯文本需要首先转换为 HTML。这可以通过 remarkrehype 实现。

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>
}

remarkrehype 生态系统包含用于 语法高亮链接标题生成目录 等的插件。

当使用上述 @next/mdx 时,您 不需要 直接使用 remarkrehype,因为它已为您处理。我们在此描述它是为了更深入地了解 @next/mdx 包在底层的工作方式。

使用基于 Rust 的 MDX 编译器(实验性)

Next.js 支持一个新的基于 Rust 的 MDX 编译器。此编译器仍处于实验阶段,不建议在生产环境中使用。要使用新编译器,您需要在将 next.config.js 传递给 withMDX 时进行配置:

next.config.js
module.exports = withMDX({
  experimental: {
    mdxRs: true,
  },
})

有用的链接

On this page