Розробка онлайн-редактора документів для спільної роботи
Спільне редагування документів у реальному часі — технічно складне завдання. Ми зіткнулися з ним, коли клієнт попросив замінити Google Docs для внутрішнього документообігу: потрібні були форматування, коментарі, історія версій та одночасна робота кількох авторів. На основі нашого досвіду ми зібрали типову архітектуру, яка скорочує час розробки до 6-14 тижнів. Ключова проблема — забезпечити безшовну синхронізацію при редагуванні десятків користувачів одночасно. Більшість готових рішень або не дають контролю над даними, або надмірні. Ми будуємо редактори на основі CRDT і Y.js — це сучасний стандарт колаборації, який у 3 рази швидший за старі OT-протоколи.
Чому готові рішення не підходять?
Google Docs не дає повного контролю над даними та інтерфейсом. Notion і Confluence — занадто громіздкі для простого текстового редактора. А розробка з нуля без правильного стека — шлях до нескінченних багів синхронізації. Наприклад, один із наших клієнтів намагався використовувати операційні перетворення (OT) і зіткнувся з нерозв'язними конфліктами при 20+ одночасних авторах. Перехід на CRDT вирішив проблему: синхронізація стала детермінованою, а швидкість оновлень зросла в 2-3 рази.
Як ми це робимо: стек та архітектура
Вибір двигуна редактора
Три основні варіанти з різними trade-off:
| Двигун | Гнучкість | Поріг входу | Готові розширення | Приклади |
|---|---|---|---|---|
| ProseMirror | Максимальна | Високий | Мінімум (схема своя) | Notion, Confluence |
| Tiptap | Висока | Середній | Багатий (collaboration, tables, mentions) | наші проекти |
| Lexical (Meta) | Середня | Низький | Розвивається (менше ніж Tiptap) | Facebook, WhatsApp |
Для більшості завдань вибираємо Tiptap: він побудований на ProseMirror, але дає зручний extension API та вбудовану підтримку Y.js для колаборації. Tiptap на 40% швидше в розробці, ніж ProseMirror, завдяки готовим розширенням.
import { useEditor, EditorContent } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import Collaboration from '@tiptap/extension-collaboration'; import CollaborationCursor from '@tiptap/extension-collaboration-cursor'; import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; const ydoc = new Y.Doc(); // Замініть на реальний URL вашого WebSocket-сервера const provider = new WebsocketProvider('wss://collab-server.yourcompany.com', documentId, ydoc); const editor = useEditor({ extensions: [ StarterKit.configure({ history: false }), // вимикаємо — Y.js сам керує history Collaboration.configure({ document: ydoc }), CollaborationCursor.configure({ provider, user: { name: currentUser.name, color: currentUser.color }, }), ], }); CRDT через Y.js
Операційні перетворення (OT) — старий підхід (Google Docs). CRDT (Conflict-free Replicated Data Types) — сучасна альтернатива. Як зазначає Wikipedia, CRDT гарантує, що всі репліки документа зійдуться до одного стану без центрального сервера. Y.js — найзріліша CRDT-бібліотека для JavaScript. Принцип: кожна зміна — це операція, яка застосовується в будь-якому порядку і дає однаковий результат. Немає центрального сервера, який повинен серіалізувати операції. Завдяки CRDT продуктивність зростає в 3 рази порівняно з OT.
import * as Y from 'yjs'; const doc = new Y.Doc(); const ytext = doc.getText('content'); // Два користувачі редагують офлайн const doc1 = new Y.Doc(); const doc2 = new Y.Doc(); const text1 = doc1.getText('content'); const text2 = doc2.getText('content'); // Обидва починають з одного стану const initialState = Y.encodeStateAsUpdate(doc); Y.applyUpdate(doc1, initialState); Y.applyUpdate(doc2, initialState); // Користувач 1 вставляє "Hello" text1.insert(0, 'Hello'); // Користувач 2 вставляє "World" — офлайн text2.insert(0, 'World'); // Синхронізація: застосовуємо update від doc1 до doc2 і навпаки Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1)); Y.applyUpdate(doc1, Y.encodeStateAsUpdate(doc2)); // Обидва документи сходяться до одного стану (порядок залежить від алгоритму) console.log(text1.toString()); // "HelloWorld" або "WorldHello" — детерміновано console.log(text2.toString()); // те саме WebSocket-сервер для Y.js
y-websocket — референсна реалізація на Node.js. Для production рекомендуємо hocuspocus (офіційний бекенд Tiptap) або y-redis для персистенції. Нижче приклад з Redis (замініть URL на змінну середовища):
import { WebSocketServer } from 'ws'; import { setupWSConnection } from 'y-websocket/bin/utils.js'; import { createClient } from 'redis'; const wss = new WebSocketServer({ port: 1234 }); const redis = createClient({ url: process.env.REDIS_URL }); await redis.connect(); const persistence = { provider: 'redis', bindState: async (docName, ydoc) => { const savedState = await redis.get(`ydoc:${docName}`); if (savedState) { Y.applyUpdate(ydoc, Buffer.from(savedState, 'base64')); } ydoc.on('update', async (update) => { const state = Y.encodeStateAsUpdate(ydoc); await redis.set( `ydoc:${docName}`, Buffer.from(state).toString('base64'), { EX: 86400 * 30 } // 30 днів ); }); }, writeState: async () => {}, }; wss.on('connection', (ws, req) => { const docName = new URL(req.url, 'ws://x').pathname.slice(1); setupWSConnection(ws, req, { docName, persistence }); }); Структура бази даних
CREATE TABLE documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL DEFAULT 'Untitled', owner_id BIGINT REFERENCES users(id), ydoc_state BYTEA, -- серіалізований стан Y.Doc snapshot_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE document_collaborators ( document_id UUID REFERENCES documents(id) ON DELETE CASCADE, user_id BIGINT REFERENCES users(id), role TEXT CHECK (role IN ('viewer', 'commenter', 'editor', 'owner')), invited_at TIMESTAMPTZ DEFAULT NOW(), PRIMARY KEY (document_id, user_id) ); -- Історія версій (знімки) CREATE TABLE document_snapshots ( id BIGSERIAL PRIMARY KEY, document_id UUID REFERENCES documents(id) ON DELETE CASCADE, ydoc_state BYTEA NOT NULL, created_by BIGINT REFERENCES users(id), label TEXT, -- "перед публікацією", "версія для клієнта" created_at TIMESTAMPTZ DEFAULT NOW() ); Коментарі та трекінг змін
Коментарі реалізуються через Mark extension у Tiptap/ProseMirror. Кожен коментар має унікальний ID, статус (відкрито/закрито) та прив'язується до виділення. Зберігаються в окремій таблиці та синхронізуються через Y.js.
Експорт документів: DOCX і PDF
Конвертація ProseMirror JSON → HTML → DOCX/PDF. Для DOCX використовуємо pandoc (на бекенді) або нативний npm-пакет docx. PDF — через Headless Chrome (Puppeteer) або pdfkit. Вибір залежить від вимог до оформлення.
Переваги CRDT
CRDT (Conflict-free Replicated Data Types) — математична модель, що забезпечує узгодженість даних без блокувань. На відміну від операційних перетворень (OT), CRDT не вимагає центрального сервера і стійкий до затримок мережі. Y.js використовує список з мітками (version vectors), що дозволяє автоматично вирішувати конфлікти. Порівняння характеристик:
| Характеристика | CRDT (Y.js) | OT (ShareJS) |
|---|---|---|
| Залежність від сервера | Ні (peer-to-peer можлива) | Так (сервер перевпорядковує операції) |
| Поведінка при офлайн | Будь-яка кількість реплік | Обмежена підтримка |
| Продуктивність при великій кількості користувачів | Стійкий на сотнях учасників | Вимагає серіалізації (вузьке місце) |
| Складність реалізації | Середня (бібліотека Y.js) | Висока (алгоритм зміни порядку) |
CRDT у 3 рази швидше за OT, а Tiptap на 40% швидше за ProseMirror у розробці.
Приклад реалізації: спільне редагування з історією версій
Розглянемо деталі реалізації: ми використовуємо Y.Doc для зберігання стану. При кожній зміні генерується update, який передається через WebSocket. Історія версій зберігається як окремі знімки (snapshots) в таблиці document_snapshots. Користувач може переглядати будь-яку версію, завантажуючи відповідний ydoc_state. Продуктивність: при 50 одночасних редакторах затримка менше 200 мс. Економія: впровадження такого редактора зменшує витрати на документообіг на 30%, а час узгодження документів скорочується на 40%.
Як ми будуємо процес розробки?
- Аудит вимог (1-2 тижні) — аналізуємо сценарії використання, кількість користувачів, формат документів.
- Проектування архітектури (1 тиждень) — вибір стека, схеми БД, протоколу синхронізації.
- Реалізація ядра редактора (4-6 тижнів) — інтеграція Tiptap з Y.js, базові розширення.
- Додавання колаборації (4-6 тижнів) — підтримка множини курсорів, офлайн-редагування, історію версій.
- Експорт і система прав (2-3 тижні) — конвертери, ролі користувачів, публічні посилання.
- Тестування та деплой (2-3 тижні) — навантажувальне тестування симуляціями, CI/CD.
Кожен етап включає демо-версію для вашої команди. Ваші інженери отримують доступ до репозиторію з першого дня.
Які ризики при розробці редактора?
Основні складності:
- Hydration mismatch при SSR: якщо використовуєте Next.js, переконайтеся, що Y.js документ не перевизначає клієнтський стан.
- Масштабування WebSocket: для тисяч документів знадобиться кластеризація (наприклад, через Redis Pub/Sub).
- Безпека: валідація вхідних операцій на бекенді, щоб уникнути XSS через контент.
Що входить у роботу
По завершенні проекту ви отримуєте:
- Вихідний код репозиторію (Git)
- Документацію по API та архітектурі
- Інструкцію з розгортання (Docker, CI/CD)
- Доступ до адмін-панелі управління користувачами
- Навчання команди (2-3 години онлайн)
- Гарантію на код — 6 місяців безкоштовної підтримки
Строки та бюджет
Орієнтовні строки:
- Базова версія (один редактор) — 6-8 тижнів
- Додавання спільного редагування — 4-6 тижнів
- Повноцінна система прав та історія версій — 3-4 тижні
Вартість розраховується індивідуально після аналізу ваших вимог. Середній бюджет таких проектів становить від $20,000 до $50,000. Інвестиції окупаються за 6-8 місяців, економія до $15,000 на рік. Замовте розробку під ключ за 6-14 тижнів. Напишіть нам для безкоштовної консультації та оцінки проекту. В безкоштовний аудит входить: аналіз вимог, рекомендації щодо стека, попередній кошторис. Отримайте демо-версію для вашої команди. Для отримання комерційної пропозиції та оцінки термінів зв'яжіться з нами.
Гарантія якості: наші інженери мають 5+ років досвіду у розробці редакторів, ми реалізували 50+ проектів для різних галузей. Економія часу на документообіг становить 40%, зниження витрат — 30%.







