Год назад мы заметили, что 60% пользователей открывают документ и в первые 30 секунд закрывают его, не сделав ни одного редактирования. Метрика не выросла, как мы её ни крутили: рекламные тексты, онбординг, шаблоны. Тогда мы пошли смотреть на сессии в продакшен-логах и поняли неприятную вещь — редактор тормозил даже на пустом документе.
Что было не так
Старый редактор v1 был построен на жирной модели документа: каждый абзац — отдельный React-компонент со своим стейтом, у каждого блока — обвязка из обработчиков, контекст-меню и тулбары рендерились вместе с документом. Открытие 30-страничного файла подвешивало главный поток на ~700 мс. На слабых устройствах — больше секунды.
Хуже всего было с курсором соавтора. На каждое обновление позиции — full re-render документа. Когда работали трое, на экране была судорога.
Что мы сделали
1. Виртуализованный документ
Мы переписали ядро редактора так, чтобы DOM рендерил только видимую часть документа плюс небольшой буфер. Скролл вниз — добавляем блоки, скролл вверх — пересоздаём. Память на больших документах упала с сотен мегабайт до десятков.
2. Отдельный слой для курсоров
Курсоры соавторов теперь живут в overlay-слое над документом — позиционируются абсолютно по координатам глифа. Обновление позиции стоит копейки и не трогает контентный DOM.
3. Operational Transforms вместо CRDT
Подробнее об этом — в отдельной заметке. Если коротко: OT-сервер у нас держит линеаризацию, клиент шлёт ему дельты, сервер возвращает преобразованную версию. Конфликтов меньше, чем казалось.
4. WASM-ядро для тяжёлых операций
Поиск с заменой, проверка орфографии, экспорт — теперь в WebAssembly. На больших документах это разница между «работает» и «зависает».
Что мы получили
- Среднее время от открытия документа до первого редактирования: 4.8 с → 1.3 с.
- Память на документе на 100 страниц: 380 МБ → 64 МБ.
- Доля сессий с быстрым отказом (закрытие за 30 с без действий): 60% → 22%.
Метрика «время до первого редактирования» оказалась лучшим предсказателем удержания, чем любые опросники. Если человек начал писать — он останется.
Что было больно
Миграция документов оказалась сложнее самой переписки. Старый формат блоков не маппился на новый один к одному — пришлось писать конвертер с тестами на каждый граничный случай. Самым жёстким был кейс с вложенными таблицами в списках — там мы две недели чинили баги, которые ловили только реальные пользователи.
Ещё одна вещь, которую мы недооценили — расширения браузеров. Половина наших баг-репортов после релиза оказалась из-за грамматических корректоров, которые лезут в contenteditable и ломают нашу модель. Пришлось переписать обработку beforeinput и публично объяснить, какие расширения мы поддерживаем.
Что дальше
На очереди — мобильный редактор с полным паритетом. Сейчас он держит ~80% функций десктопа, но критичные вещи вроде комментариев работают через костыли. К концу Q2 планируем закрыть.