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, позволяющее писать JSX прямо в файлах Markdown. Это мощный способ добавления динамической интерактивности и встраивания React-компонентов в ваш контент.
Next.js поддерживает как локальный MDX-контент внутри приложения, так и удалённые MDX-файлы, загружаемые динамически на сервере. Плагин Next.js преобразует Markdown и React-компоненты в HTML, включая поддержку использования в серверных компонентах (по умолчанию в 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
Создайте файл mdx-components.tsx
в корне вашего приложения (src/
или родительской папке app/
):
Важно:
mdx-components.tsx
обязателен для использования MDX с App Router и не будет работать без него.
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)
Затем создайте новую MDX-страницу в директории /app
:
your-project
├── app
│ └── my-mdx-page
│ └── page.mdx
└── package.json
Теперь вы можете использовать Markdown и импортировать React-компоненты прямо в MDX-странице:
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 { 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 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)
Фронтматтер
Фронтматтер — это YAML-подобные пары ключ/значение, которые можно использовать для хранения данных о странице. @next/mdx
не поддерживает фронтматтер по умолчанию, но есть решения для его добавления:
Для доступа к метаданным страницы с @next/mdx
вы можете экспортировать объект метаданных из .mdx
-файла:
export const metadata = {
author: 'Иван Иванов',
}
# Моя 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, { ImageProps } 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 as ImageProps)}
/>
),
...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
:
module.exports = withMDX({
experimental: {
mdxRs: true,
},
})