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

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

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

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

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

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

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

Часто задаваемые вопросы

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

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

Ошибка 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-редактор, соответствующий современным стандартам безопасности и производительности.