Remix и MDX

MDX — это расширение Markdown, позволяющее вставлять JSX-компоненты прямо в текст. В связке с Remix это открывает возможности создания интерактивной документации, блогов и образовательного контента с компонентами React. Для корректной работы MDX в Remix необходимо понимать несколько ключевых аспектов: конфигурацию сборки, маршрутизацию, обработку данных и рендеринг компонентов.

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

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

npm install @mdx-js/react @mdx-js/loader

Remix изначально использует Vite или Webpack (в зависимости от версии проекта). Для Vite необходимо настроить MDX как плагин:

import mdx from '@mdx-js/rollup';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react(),
    mdx()
  ]
});

Для Webpack используется @mdx-js/loader:

module.exports = {
  module: {
    rules: [
      {
        test: /\.mdx$/,
        use: [
          'babel-loader',
          '@mdx-js/loader'
        ]
      }
    ]
  }
};

Это обеспечивает корректное преобразование .mdx файлов в React-компоненты, которые можно импортировать в маршруты Remix.

Структура проекта

Обычно MDX-файлы размещают в папке app/routes/content или аналогичной. Пример структуры:

app/
 ├─ routes/
 │   ├─ blog/
 │   │   ├─ index.tsx
 │   │   └─ post-1.mdx
 └─ components/
     └─ Highlight.tsx

В файле маршрута можно импортировать MDX-компонент напрямую:

import Post1 from './post-1.mdx';

export default function BlogPost() {
  return (
    <article>
      <Post1 components={{ Highlight }} />
    </article>
  );
}

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

MDX позволяет заменять стандартные HTML-теги на React-компоненты. Это делается через объект components, передаваемый в MDX-компонент:

const components = {
  h1: (props) => <h1 style={{ color: 'tomato' }} {...props} />,
  pre: (props) => <pre className="custom-pre" {...props} />,
  Highlight
};

<Post1 components={components} />

В этом примере все заголовки <h1> будут окрашены в томато, а блоки кода обернуты в кастомный компонент Highlight. Такой подход позволяет единообразно стилизовать контент и внедрять интерактивные элементы.

Загрузка данных и маршруты

Remix предлагает функцию loader для предзагрузки данных. В сочетании с MDX можно динамически подгружать контент:

import fs from 'fs';
import path from 'path';
import { json } from '@remix-run/node';
import { MDXContent } from '@mdx-js/react';

export const loader = async () => {
  const filePath = path.join(process.cwd(), 'app/routes/blog/post-1.mdx');
  const source = fs.readFileSync(filePath, 'utf-8');
  return json({ source });
};

export default function BlogPost({ data }) {
  return (
    <article>
      <MDXContent components={{ Highlight }} children={data.source} />
    </article>
  );
}

Такой подход позволяет разделять контент и логику рендеринга, облегчая масштабирование сайта и добавление новых статей.

Интерактивные элементы

MDX открывает возможности для интеграции сложных React-компонентов:

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

В MDX-файле можно использовать компонент прямо в тексте:

# Интерактивный счетчик

<Counter />

Это делает контент живым и динамичным, что особенно полезно для образовательных и технических блогов.

Настройка маршрутизации для динамических MDX-файлов

Для создания блога с динамическими страницами можно использовать параметризованные маршруты Remix:

app/routes/blog/$slug.tsx

Внутри маршрута читается соответствующий MDX-файл на основе параметра slug:

import fs from 'fs';
import path from 'path';
import { useParams } from '@remix-run/react';
import { MDXRemote } from 'next-mdx-remote';

export default function Post() {
  const { slug } = useParams();
  const filePath = path.join(process.cwd(), `app/routes/blog/${slug}.mdx`);
  const source = fs.readFileSync(filePath, 'utf-8');

  return <MDXRemote {...source} components={{ Highlight }} />;
}

Это позволяет создавать блоги, документацию и другие проекты, где новые страницы добавляются простым добавлением MDX-файла без изменения маршрутов.

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

Для крупных проектов рекомендуется:

  • Использовать remark и rehype плагины для автоматической обработки MDX (например, синтаксическая подсветка, таблицы содержимого).
  • Разбивать контент на модули и компоненты для уменьшения размера бандла.
  • Кэшировать скомпилированные MDX-файлы на сервере, чтобы избежать повторной компиляции при каждом запросе.

Рекомендации по стилю

  • Разделять презентацию (React-компоненты) и контент (MDX), чтобы обеспечить переиспользуемость.
  • Использовать единый набор кастомных компонентов для заголовков, кода и изображений.
  • Для интерактивного контента выделять отдельные MDX-файлы с примерами, чтобы основной текст оставался легким и читаемым.

Такой подход обеспечивает структурированность, масштабируемость и легкость поддержки проектов на Remix с MDX.