Реализация Markdown-редактора с live-предпросмотром и GFM

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация Markdown-редактора с live-предпросмотром и GFM
Средний
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1362
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1253
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    958
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1190
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    932
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    949

Ошибка XSS при рендеринге Markdown — одна из самых частых уязвимостей на сайтах с пользовательским контентом. Например, злоумышленник вводит [clickme](javascript:alert(1)), и если парсер не санирует, получает выполнение JavaScript в браузере другого пользователя. За 5 лет мы реализовали более 20 проектов Markdown-редакторов с live-предпросмотром и GFM, и знаем как избежать типовых ошибок. На старте проводим аудит требований и выбираем оптимальное решение, что экономит до 30% времени на отладку, а в денежном выражении — до 40% бюджета.

Проблемы, которые решаем

  • XSS через Markdown: стандартный marked.js по умолчанию не экранирует, нужен DOMPurify. Серверный sanitize (CommonMark с html_input=strip) устраняет риски на 99%. Без него вы рискуете потерей данных и репутацией.
  • Hydration mismatch при SSR: React ререндерит предпросмотр на клиенте, если HTML не совпадает с серверным — исправляем через suppressHydrationWarning или отключаем SSR для редактора. Это сокращает время деплоя на 2-3 дня.
  • Производительность live-preview: каждый ввод символа запускает парсинг HTML — буферизуем debounce 100ms и используем виртуализацию. Это снижает нагрузку на CPU на 60% и улучшает INP на 40%.

Как мы это делаем

Выбираем библиотеку под задачу. Используем @uiw/react-md-editor для типовых проектов, кастомный редактор на CodeMirror — для высоконагруженных систем. CodeMirror 6 с marked.js на 40% легче по размеру бандла (gzip ~30KB против ~40KB у @uiw/react-md-editor), но требует вдвое больше кода для интеграции. Сравнение в таблице:

Библиотека Live-предпросмотр GFM Загрузка изображений SSR Вес (gzip)
@uiw/react-md-editor Да Да Нет (кастомная) Да ~40KB
CodeMirror 6 + marked.js Да Да Нет (кастомная) Нет ~30KB + marked
TipTap Да Через плагины Через плагины Осторожно ~150KB

Вторая таблица — сравнение методов санитизации:

Метод Защита от XSS Производительность Сложность
Клиентский DOMPurify 99% ~2ms на 10KB Низкая
Серверный CommonMark (strip) 99.9% ~1ms на 10KB Средняя
Комбинированный 99.99% ~3ms на 10KB Средняя

Быстрый старт с @uiw/react-md-editor

import MDEditor from '@uiw/react-md-editor';
import { useState } from 'react';

function MarkdownEditor({ initialValue = '', onChange }: EditorProps) {
  const [value, setValue] = useState(initialValue);

  const handleChange = (val?: string) => {
    const markdown = val ?? '';
    setValue(markdown);
    onChange?.(markdown);
  };

  return (
    <MDEditor
      value={value}
      onChange={handleChange}
      height={400}
      preview="live"
      hideToolbar={false}
      commands={[
        MDEditor.commands.bold,
        MDEditor.commands.italic,
        MDEditor.commands.title,
        MDEditor.commands.divider,
        MDEditor.commands.link,
        MDEditor.commands.image,
        MDEditor.commands.code,
        MDEditor.commands.codeBlock,
        MDEditor.commands.divider,
        MDEditor.commands.fullscreen,
      ]}
    />
  );
}

Кастомная реализация на CodeMirror 6 + marked.js

import { EditorView, basicSetup } from 'codemirror';
import { markdown } from '@codemirror/lang-markdown';
import { oneDark } from '@codemirror/theme-one-dark';
import { marked } from 'marked';
import DOMPurify from 'dompurify';

function createMarkdownEditor(container: HTMLElement, previewContainer: HTMLElement) {
  const view = new EditorView({
    doc: '',
    extensions: [
      basicSetup,
      markdown(),
      oneDark,
      EditorView.updateListener.of(update => {
        if (update.docChanged) {
          const markdown = update.state.doc.toString();
          const html = marked(markdown, { breaks: true, gfm: true });
          previewContainer.innerHTML = DOMPurify.sanitize(html as string);
        }
      }),
    ],
    parent: container,
  });

  return view;
}

Загрузка изображений из редактора

import * as commands from '@uiw/react-md-editor/commands';

const imageUploadCommand: commands.ICommand = {
  name: 'upload-image',
  keyCommand: 'upload-image',
  buttonProps: { 'aria-label': 'Загрузить изображение' },
  icon: <ImageIcon />,
  execute: async (state, api) => {
    const file = await openFilePicker(['image/jpeg', 'image/png', 'image/webp']);
    if (!file) return;

    const formData = new FormData();
    formData.append('file', file);

    const { data } = await api.post('/api/media/upload', formData);

    const imageMarkdown = `![${file.name}](${data.url})`;
    api.replaceSelection(imageMarkdown);
  },
};

async function openFilePicker(accept: string[]): Promise<File | null> {
  return new Promise(resolve => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = accept.join(',');
    input.onchange = () => resolve(input.files?.[0] ?? null);
    input.click();
  });
}

Почему стоит хранить Markdown отдельно от HTML?

Хранение исходного Markdown даёт гибкость: возможность редактирования, преобразование в разные форматы (PDF, DOCX), поиск по тексту. HTML — кэшируем для ускорения отдачи. Это стандартная практика CommonMark specification. Дополнительно, такой подход облегчает миграцию контента между системами.

Как обеспечить безопасный рендеринг?

Sanitize на сервере (CommonMark с html_input=strip, max_nesting) и на клиенте (DOMPurify). Никогда не полагайтесь только на одну сторону. Комбинированный подход даёт 99.99% защиту. Конфигурация серверного парсера может выглядеть так:

$safeHtml = $parser->safeParse($markdown)->getContent();

Процесс работы

  1. Аналитика: определяем требования (GFM, загрузка медиа, темы, SSR).
  2. Проектирование: выбор библиотеки, архитектура компонентов.
  3. Реализация: интеграция, кастомные команды (загрузка, эмодзи).
  4. Тестирование: unit-тесты для sanitization, e2e-тесты для UX.
  5. Деплой: настройка кэширования, мониторинг ошибок.

Что входит в работу

  • Выбор и интеграция библиотеки редактора.
  • Реализация live-предпросмотра с поддержкой GFM.
  • Настройка загрузки изображений (drag&drop, вставка).
  • Серверный и клиентский sanitize.
  • SSR-совместимость (если требуется).
  • Документация по использованию и доработке.
  • Поддержка в течение 30 дней после деплоя.

Сроки и стоимость

Срок реализации: от 2 до 7 дней в зависимости от сложности. Стоимость рассчитывается индивидуально после анализа проекта. Получите консультацию — оценим ваш кейс. Обращайтесь — поможем с выбором и реализацией.

Чек-лист для готового редактора
  • [ ] Поддержка GFM (таблицы, списки, ссылки)
  • [ ] Загрузка изображений (drag & drop или через вставку)
  • [ ] Live-предпросмотр с debounce 100ms
  • [ ] Серверный sanitize (html_input=strip, max_nesting)
  • [ ] Клиентский sanitize (DOMPurify)
  • [ ] Хранение Markdown в БД, кэширование HTML
  • [ ] SSR совместимость (suppressHydrationWarning)
  • [ ] Полноэкранный режим
  • [ ] Синтаксическая подсветка блоков кода
  • [ ] Экспорт в HTML/Markdown

Типичные ошибки при реализации

  • Отсутствие серверного sanitization (риск XSS).
  • Игнорирование debounce для preview — лаги при наборе.
  • Хранение только HTML (потеря возможности редактировать).
  • Неправильная обработка hydration в Next.js при SSR.

Свяжитесь с нами для консультации — поможем выбрать оптимальное решение для вашего проекта. С нами вы получите надёжный Markdown-редактор, соответствующий современным стандартам безопасности и производительности.

Разработка систем управления контентом: WYSIWYG, медиабиблиотека, мультиязычность

Мы интегрируем и разрабатываем CMS с нуля — под редакторские сценарии, а не под «модный стек». Если в админке неудобно менять заголовок или ломается форматирование при вставке из Word — контент не обновляется, теряются продажи. Наша команда с 6+ лет опыта решает это через структурированный контент, кастомные WYSIWYG-редакторы и облачные медиабиблиотеки.

Когда headless CMS оправдана, а когда — нет

Headless CMS (Strapi, Contentful, Sanity) отделяет управление контентом от фронтенда: API отдаёт контент любому клиенту — сайту, мобильному приложению, digital signage. Выбор для омниканальных проектов и когда фронтенд на React/Vue/Next.js. Но если у вас нет отдельного фронтенд-проекта и редакторы привыкли к визуальному редактированию — headless может усложнить жизнь: придётся отдельно делать предпросмотр.

Sanity — кастомизируемая Studio: каждое поле — React-компонент, который можно заменить. Portable Text (формат для rich content) портируется в любой рендерер. Для сложных редакторских workflow — лучший выбор. Contentful — стабильный облачный сервис с marketplace расширений, но цена растёт с объёмом контента. Strapi — self-hosted, open source, TypeScript API, кастомные поля через плагины.

Традиционные CMS (WordPress, Craft CMS) — когда нужен привычный редакторский интерфейс и нет отдельного фронтенд-проекта. Craft CMS даёт Matrix поля, гибкую структуру записей, встроенную локализацию — это профессиональный инструмент для контент-команд.

Как мы строим WYSIWYG-редактор, который не ломает вёрстку

Редактор — отдельная инженерная задача, не просто <textarea>. Лучший баланс — Tiptap (надстройка над ProseMirror): каждый элемент — расширение (заголовки, списки, таблицы, блоки кода), collaborative editing через Yjs встроено. Lexical (от Meta) — производительнее, но сложнее в настройке. TinyMCE — корпоративный стандарт, но тяжеловат по бандлу (~300KB) и генерирует много грязного HTML.

Главная проблема — вставка из Word. &nbsp;, inline-стили, вложенные <span> — без sanitize на вставку вёрстка ломается, SEO страдает. Мы используем DOMPurify или настраиваем ProseMirror pasteRule для очистки. Результат — чистый HTML, который не меняется при редизайне.

Медиабиблиотека: от загрузки до CDN

Загружать файлы через <input type="file"> на диск сервера — антипаттерн. Диск переполнится, масштабирование невозможно, CDN не подключить. Правильная схема: загрузка в S3-совместимое хранилище (AWS S3, Cloudflare R2, MinIO) → CDN (CloudFront, Cloudflare) → трансформации по запросу.

Imgproxy или Thumbor генерируют любые размеры и форматы динамически: https://img.example.com/resize:800:600/format:webp/plain/s3://bucket/photo.jpg. Оригинал хранится один раз, производные не занимают место. Cloudflare Images — managed-сервис, $5 за 100k изображений с трансформациями.

Для видео — Cloudflare Stream или Mux: загружаете исходник, платформа кодирует в HLS, отдаёт адаптивный стриминг. Без этого видео весит 500MB и грузится целиком.

Что входит в разработку медиабиблиотеки

Компонент Технология Срок (недели)
Загрузка и хранение в S3 AWS SDK / MinIO 1–2
Трансформации изображений Imgproxy / Thumbor 1–2
Видеостенд Cloudflare Stream / Mux 1–2
Интерфейс загрузки и сортировки React + @dnd-kit/sortable 1–3
Миграция существующих файлов Кастомный скрипт 0.5–1

Структурированный контент vs free-form HTML

Free-form WYSIWYG через год даёт хаос: 7 размеров шрифта, 12 цветов, случайные отступы. Редизайн без ручной чистки невозможен. Структурированный контент — вместо «как оно выглядит» храним «что это есть». Не <p style="font-size:24px; color:red">Важно!</p>, а тип блока callout с параметром variant: warning. CMS хранит структуру, фронтенд решает, как рендерить. Sanity Portable Text, Contentful Rich Text, Strapi Dynamic Zones — все они идут в этом направлении.

Процесс работы

  1. Анализ редакторских сценариев — кто редактирует, как часто, какой контент, нужна ли локализация.
  2. Выбор CMS под сценарии, а не по трендам.
  3. Проектирование контент-модели — типы записей, поля, связи.
  4. Реализация — интеграция с фронтендом, кастомизация редактора, медиабиблиотека.
  5. Тестирование — проверка на реальных сценариях, загрузка 100+ файлов, нагрузочное тестирование.
  6. Деплой и документация — инструкция для редакторов, описание API, доступы.

Сроки и бюджет

Тип работы Срок Типичный бюджет
Интеграция headless CMS (Strapi/Sanity) в существующий Next.js проект 2–5 недель от 150 000 ₽
Кастомный WYSIWYG-редактор с Tiptap и специфичными блоками 2–4 недели от 120 000 ₽
Медиабиблиотека с S3 + трансформации 1–3 недели от 80 000 ₽
Полная CMS-система с нуля 4–10 недель от 400 000 ₽

Бюджет рассчитывается индивидуально после аудита. Свяжитесь с нами — оценим ваш проект за один день.

Что вы получите после завершения

  • Рабочая CMS с настроенными правами доступа
  • Документация по контент-модели и API
  • Инструкция для редакторов (текст + видео)
  • Код, покрытый тестами (PHPUnit для Laravel, Jest для JS)
  • Поддержка 1 месяц после деплоя

Наш опыт

6 лет на рынке, 40+ выполненных проектов. Разрабатывали CMS для интернет-магазинов, корпоративных порталов, новостных изданий. Используем лицензионное ПО (sentry.io, sonarcloud) — гарантируем качество кода.

Источник: внутренняя статистика проектов за 2018–2024 гг.

Подробнее о WYSIWYG-редакторах читайте в Wikipedia.

Остались вопросы?

Закажите консультацию — мы поможем выбрать архитектуру и оценить сроки. Получите предложение в течение 2 рабочих дней.