Next.js: MDX и Markdown обработка

Remark и Rehype — это две ключевые библиотеки для обработки Markdown и HTML в экосистеме JavaScript. Remark отвечает за синтаксический разбор Markdown, предоставляя абстрактное синтаксическое дерево (MDAST), которое можно модифицировать с помощью плагинов. Rehype работает с HTML и HAST, позволяя производить трансформации, анализ и генерацию HTML-кода.

В контексте Next.js их используют вместе с MDX, чтобы расширять возможности Markdown и интегрировать компоненты React прямо в тексты документации, блогов или страниц. MDX использует Remark для парсинга Markdown, а Rehype — для генерации HTML и работы с DOM-подобными структурами.


Установка и базовая конфигурация

Для начала нужно установить пакеты:

npm install remark remark-html rehype rehype-stringify next-mdx-remote
  • remark — ядро для работы с Markdown.
  • remark-html — плагин для преобразования MDAST в HTML.
  • rehype — ядро для работы с HTML AST.
  • rehype-stringify — преобразование HAST обратно в HTML-строку.
  • next-mdx-remote — позволяет рендерить MDX контент на сервере и клиенте Next.js.

Обработка Markdown в Next.js

Простейший пример обработки Markdown через Remark:

import { remark } from 'remark';
import html from 'remark-html';
import fs from 'fs';
import path from 'path';

const markdownPath = path.join(process.cwd(), 'content', 'example.md');
const fileContents = fs.readFileSync(markdownPath, 'utf8');

const processedContent = await remark()
  .use(html)
  .process(fileContents);

const contentHtml = processedContent.toString();
  • remark().use(html) подключает плагин, который конвертирует Markdown в HTML.
  • process(fileContents) возвращает объект с HTML в виде строки, готовый для вставки в компонент Next.js.

Использование Rehype для дополнительных трансформаций

Rehype позволяет обрабатывать HTML, например, добавлять классы к тегам или интегрировать кастомные обработки.

import { rehype } from 'rehype';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import fs from 'fs';
import path from 'path';

const htmlPath = path.join(process.cwd(), 'content', 'example.html');
const htmlContent = fs.readFileSync(htmlPath, 'utf8');

const processedHtml = await rehype()
  .data('settings', { fragment: true })
  .use(rehypeParse, { fragment: true })
  .use(() => (tree) => {
    tree.children.forEach(node => {
      if (node.tagName === 'p') {
        node.properties = { className: ['custom-paragraph'] };
      }
    });
  })
  .use(rehypeStringify)
  .process(htmlContent);

const finalHtml = processedHtml.toString();
  • rehypeParse преобразует HTML в HAST.
  • Пользовательская функция может модифицировать дерево, например, добавлять классы к тегам.
  • rehypeStringify возвращает HTML в виде строки.

MDX и интеграция с React

MDX позволяет вставлять React-компоненты прямо в Markdown. Конфигурация с next-mdx-remote:

import { serialize } from 'next-mdx-remote/serialize';
import { MDXRemote } from 'next-mdx-remote';

const mdxSource = `
# Заголовок

Нажми меня
`;

const mdxContent = await serialize(mdxSource, {
  mdxOptions: {
    remarkPlugins: [require('remark-gfm')],
    rehypePlugins: [require('rehype-slug')]
  }
});
  • remark-gfm добавляет поддержку GitHub Flavored Markdown (таблицы, задачи).
  • rehype-slug автоматически генерирует id для заголовков.
  • MDXRemote рендерит готовый контент как React-компоненты:

Расширение функциональности через плагины

Remark-плагины позволяют реализовать:

  • подсветку кода (remark-prism),
  • автогенерацию таблиц содержания (remark-toc),
  • добавление ссылок на заголовки (remark-autolink-headings).

Rehype-плагины помогают:

  • минифицировать HTML (rehype-minify),
  • обрабатывать изображения (rehype-img-size),
  • добавлять классы или атрибуты тегам (rehype-add-classes).

Пример с плагинами для подсветки кода:

import remarkPrism from 'remark-prism';

const mdxContent = await serialize(mdxSource, {
  mdxOptions: {
    remarkPlugins: [remarkPrism],
    rehypePlugins: []
  }
});

Важные нюансы и оптимизации

  • Markdown и MDX должны обрабатываться на серверной стороне (getStaticProps или getServerSideProps) для улучшения SEO и производительности.
  • MDX-компоненты требуют строгой типизации в TypeScript. Рекомендуется заранее определить все доступные компоненты.
  • Для больших проектов с сотнями MDX-файлов стоит кешировать результаты serialize или сохранять HTML в отдельной базе данных.
  • Обработка деревьев AST через кастомные функции Rehype позволяет контролировать абсолютно любой аспект HTML, включая оптимизацию для accessibility и SEO.

Интеграция с маршрутизацией Next.js

Для генерации страниц из Markdown:

export async function getStaticPaths() {
  const files = fs.readdirSync(path.join(process.cwd(), 'content'));
  const paths = files.map(filename => ({
    params: { slug: filename.replace(/\.mdx$/, '') }
  }));
  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  const filePath = path.join(process.cwd(), 'content', `${params.slug}.mdx`);
  const source = fs.readFileSync(filePath, 'utf8');

  const mdxContent = await serialize(source, {
    mdxOptions: {
      remarkPlugins: [require('remark-gfm')],
      rehypePlugins: [require('rehype-slug')]
    }
  });

  return { props: { mdxContent } };
}

Такой подход позволяет создавать статические страницы с динамическим содержимым Markdown, используя всю мощь Remark и Rehype для кастомных преобразований и оптимизации HTML.