Розробка онлайн-редактора документів для спільної роботи
Спільне редагування документів у реальному часі — технічно складне завдання. Ми зіткнулися з ним, коли клієнт попросив замінити 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%.







