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, позволяющее использовать JSX непосредственно в файлах Markdown. Это мощный способ добавить динамическую интерактивность и встраивать React-компоненты в ваш контент.

Next.js поддерживает как локальный MDX-контент внутри приложения, так и удалённые MDX-файлы, загружаемые динамически на сервере. Плагин Next.js преобразует Markdown и React-компоненты в HTML, включая поддержку использования в Server Components (по умолчанию в App Router).

@next/mdx

Пакет @next/mdx используется для настройки Next.js для обработки Markdown и MDX. Он работает с локальными файлами, позволяя создавать страницы с расширением .mdx прямо в директориях /pages или /app.

Рассмотрим, как настроить и использовать 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)

Затем создайте новую MDX-страницу в директории /pages:

  ваш-проект
  ├── pages
  │   └── my-mdx-page.mdx
  └── package.json

Теперь вы можете использовать Markdown и импортировать React-компоненты прямо в MDX-странице:

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 { 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: { source: 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: { source: 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.

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)

Фронтматтер — это YAML-подобные пары ключ/значение для хранения данных о странице. @next/mdx не поддерживает фронтматтер по умолчанию, но есть решения для его добавления, например:

Для доступа к метаданным страницы с @next/mdx можно экспортировать объект meta из .mdx-файла:

export const meta = {
  author: 'Иван Иванов',
}

# Моя MDX-страница

Пользовательские элементы

Одно из преимуществ Markdown — соответствие нативным HTML-элементам, что делает написание быстрым и интуитивным:

Это список в Markdown:

- Один
- Два
- Три

Это генерирует следующий HTML:

<p>Это список в Markdown:</p>

<ul>
  <li>Один</li>
  <li>Два</li>
  <li>Три</li>
</ul>

Для стилизации собственных элементов можно использовать шорткоды — пользовательские компоненты, соответствующие HTML-элементам.

Для этого создайте файл mdx-components.tsx в корне приложения (родительская папка для pages/ или src/) и добавьте пользовательские элементы:

import type { MDXComponents } from 'mdx/types'
import Image from 'next/image'

// Этот файл позволяет предоставлять пользовательские React-компоненты
// для использования в MDX-файлах. Можно импортировать любые
// React-компоненты, включая inline-стили,
// компоненты из других библиотек и т.д.

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-компоненты, включая inline-стили,
// компоненты из других библиотек и т.д.

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) // Преобразование в AST Markdown
    .use(remarkRehype) // Преобразование в AST HTML
    .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.

Использование MDX-компилятора на Rust (экспериментально)

Next.js поддерживает новый MDX-компилятор на Rust. Он пока экспериментальный и не рекомендуется для продакшена. Для его использования настройте next.config.js при передаче в withMDX:

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

Полезные ссылки