Next.js и MDX

Для интеграции MDX в проект на Next.js необходимо установить несколько пакетов: @next/mdx и @mdx-js/loader (или их современные аналоги для Next.js 13+). Используется конфигурация через next.config.js с применением функции withMDX. Это позволяет импортировать файлы с расширением .mdx как компоненты React.

Пример конфигурации:

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/
});

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx']
});

Важный момент: расширение файлов должно включать mdx, иначе Next.js не будет их обрабатывать как страницы.


Структура MDX-файла

MDX-файл сочетает в себе Markdown и JSX. Стандартный шаблон выглядит так:

---
title: "Пример MDX"
date: "2026-03-23"
---

# Заголовок

Это обычный текст в Markdown.

<MyComponent prop="value" />

Раздел --- в начале файла называется frontmatter и хранит метаданные, которые можно использовать в Next.js для генерации статических страниц через getStaticProps или getStaticPaths.


Импорт и использование компонентов

В MDX можно импортировать любые React-компоненты, что позволяет создавать интерактивные статьи, документацию или блог. Пример:

import Alert from '../components/Alert';

<Alert type="warning">
  Это предупреждающее сообщение.
</Alert>

Компоненты могут быть встроенными (как Alert) или глобальными, если настроить MDXProvider для всего приложения:

import { MDXProvider } from '@mdx-js/react';
import CustomH1 from '../components/CustomH1';

const components = {
  h1: CustomH1
};

export default function App({ Component, pageProps }) {
  return (
    <MDXProvider components={components}>
      <Component {...pageProps} />
    </MDXProvider>
  );
}

Таким образом, все заголовки h1 в MDX-файлах будут рендериться через компонент CustomH1.


Генерация статических страниц из MDX

Next.js позволяет использовать MDX для статических страниц. Основные функции:

  • getStaticProps для передачи контента страницы;
  • getStaticPaths для динамических маршрутов.

Пример генерации страницы блога:

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { serialize } from 'next-mdx-remote/serialize';
import { MDXRemote } from 'next-mdx-remote';

const postsDirectory = path.join(process.cwd(), 'posts');

export async function getStaticPaths() {
  const filenames = fs.readdirSync(postsDirectory);
  const paths = filenames.map((name) => ({
    params: { slug: name.replace(/\.mdx$/, '') }
  }));

  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  const filePath = path.join(postsDirectory, `${params.slug}.mdx`);
  const source = fs.readFileSync(filePath, 'utf8');
  const { content, data } = matter(source);
  const mdxSource = await serialize(content);

  return { props: { mdxSource, frontMatter: data } };
}

export default function Post({ mdxSource, frontMatter }) {
  return (
    <>
      <h1>{frontMatter.title}</h1>
      <MDXRemote {...mdxSource} />
    </>
  );
}

Здесь gray-matter извлекает frontmatter, а next-mdx-remote позволяет рендерить MDX с переданными компонентами и данными.


Настройка плагинов Remark и Rehype

MDX поддерживает плагины для расширенной обработки Markdown и HTML. В serialize можно передать опции:

import { serialize } from 'next-mdx-remote/serialize';
import remarkGfm from 'remark-gfm';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';

const mdxSource = await serialize(content, {
  mdxOptions: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings]
  }
});
  • remark-gfm добавляет поддержку таблиц, чекбоксов и других расширений GitHub Flavored Markdown.
  • rehype-slug генерирует уникальные ID для заголовков.
  • rehype-autolink-headings добавляет ссылки на заголовки для быстрой навигации.

Интерактивные примеры и динамические компоненты

MDX позволяет встроить React-компоненты с состоянием и динамикой прямо в текст. Пример счетчика:

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onCl ick={() => setCount(count + 1)}>
      Нажато {count} раз
    </button>
  );
}

<Counter />

Такой подход делает MDX идеальным инструментом для документации библиотек с живыми демо-примерами.


Организация проекта с MDX

Рекомендуется хранить все MDX-файлы в отдельной директории, например posts или docs. Для удобства можно создать утилиты для загрузки и сортировки файлов по дате или категории. Пример функции сортировки постов:

export function getSortedPosts() {
  const files = fs.readdirSync(postsDirectory);
  const posts = files.map((file) => {
    const source = fs.readFileSync(path.join(postsDirectory, file), 'utf8');
    const { data } = matter(source);
    return { slug: file.replace(/\.mdx$/, ''), ...data };
  });

  return posts.sort((a, b) => new Date(b.date) - new Date(a.date));
}

Это позволяет автоматически формировать списки статей и навигацию по сайту.


Оптимизация загрузки MDX

Для больших проектов рекомендуется:

  1. Использовать next-mdx-remote для динамической сериализации MDX, что уменьшает размер бандла.
  2. Разделять контент на отдельные компоненты.
  3. Кэшировать результаты сериализации, если страницы часто не меняются.
  4. Подключать только нужные плагины Remark/Rehype для ускорения сборки.

Работа с TypeScript и MDX

MDX полностью совместим с TypeScript. Можно создавать компоненты с типами и импортировать их в MDX-файлы:

type AlertProps = {
  type: 'info' | 'warning';
  children: React.ReactNode;
};

const Alert: React.FC<AlertProps> = ({ type, children }) => (
  <div className={`alert ${type}`}>{children}</div>
);

export default Alert;

MDX корректно подхватывает типы при импорте и обеспечивает автодополнение в редакторах.


Заключение по функциональности

MDX в связке с Next.js объединяет статический и динамический контент, позволяет использовать React-компоненты прямо в Markdown, поддерживает frontmatter для метаданных и легко интегрируется с Remark и Rehype. Это делает его идеальным инструментом для создания документации, блогов, интерактивных руководств и образовательных платформ.