Static site generation

MDX — это расширение Markdown, позволяющее встраивать JSX-компоненты прямо в текстовую разметку. В сочетании с современными фреймворками для Static Site Generation (SSG), такими как Next.js или Gatsby, MDX становится мощным инструментом для построения статических сайтов с динамическими компонентами.

Принцип работы SSG с MDX

Static Site Generation предполагает предкомпиляцию страниц на этапе сборки проекта. При использовании MDX каждая .mdx-страница сначала преобразуется в React-компонент, который затем рендерится в HTML. Это обеспечивает:

  • Высокую производительность за счёт предварительной генерации страниц.
  • SEO-дружелюбность, так как поисковые роботы видят полностью готовый HTML.
  • Безопасность, поскольку на клиент не передаётся исходный JSX-код.

Процесс выглядит следующим образом:

  1. MDX-файл читается сборщиком (например, @next/mdx для Next.js или gatsby-plugin-mdx для Gatsby).
  2. Контент конвертируется в React-компонент с поддержкой всех импортируемых JSX-компонентов.
  3. Компонент рендерится в HTML на этапе сборки.
  4. Сгенерированные HTML-страницы раздаются пользователю через CDN.

Подключение MDX к статической генерации

В Next.js это достигается через getStaticProps и getStaticPaths. Пример:

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

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 fullPath = path.join(postsDirectory, `${params.slug}.mdx`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');
  const { content, data } = matter(fileContents);
  const mdxSource = await serialize(content, { scope: data });
  return { props: { mdxSource, frontMatter: data } };
}

export default function Post({ mdxSource, frontMatter }) {
  return (
    

{frontMatter.title}

); }

Здесь:

  • fs и path читают локальные файлы MDX.
  • gray-matter извлекает метаданные (frontMatter) из MDX.
  • next-mdx-remote сериализует MDX-контент в форму, готовую для рендеринга React-компонентом.

Использование кастомных компонентов в MDX

MDX позволяет импортировать и использовать React-компоненты прямо внутри Markdown. Для SSG это означает возможность включать интерактивные элементы без потери преимуществ статической генерации.

Пример:

// components/Alert.js
export default function Alert({ children, type }) {
  return 
{children}
; } // pages/posts/[slug].js import Alert from '../. ./components/Alert'; const components = { Alert }; ;

В MDX-файле:

# Важное сообщение


  Это предупреждение!

Статическая генерация преобразует это в HTML, а React-компонент Alert подключается для интерактивности на клиенте, если необходимо.

Оптимизация производительности при SSG с MDX

  1. Кэширование сериализованных MDX: сериализация MDX может быть ресурсоёмкой на больших сайтах. Использование кэширования ускоряет сборку.
  2. Разделение контента по страницам: вместо единого большого MDX-файла лучше создавать отдельные файлы для каждой статьи или раздела.
  3. Lazy loading компонентов: тяжёлые React-компоненты можно загружать асинхронно через next/dynamic.
  4. Минимизация сторонних зависимостей: каждая подключённая библиотека увеличивает размер конечного HTML и JS.

Преимущества MDX в SSG

  • Смешение Markdown и JSX даёт универсальность: текстовая документация и интерактивные элементы в одном файле.
  • Типизация компонентов через TypeScript повышает надёжность при сборке.
  • Лёгкость интеграции с CMS: MDX-файлы можно хранить в Git, подключать через headless CMS или генерировать динамически при сборке.
  • Поддержка плагинов Remark и Rehype, что позволяет автоматически оптимизировать контент, добавлять таблицы, ссылки и кодовые блоки.

Работа с динамическими данными

Даже при статической генерации MDX поддерживает использование данных, полученных на этапе сборки:

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  const mdxSources = await Promise.all(
    posts.map(async (post) => {
      return {
        slug: post.slug,
        mdxSource: await serialize(post.content, { scope: post }),
      };
    })
  );

  return { props: { mdxSources } };
}

Таким образом, можно комбинировать преимущества статической генерации с актуальными данными, загруженными из API.