Реализация Real-Time совместного редактирования на сайте (Yjs/Liveblocks)

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

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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Real-Time совместного редактирования на сайте (Yjs/Liveblocks)
Сложная
~2-4 недели
Часто задаваемые вопросы

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

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

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

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

Реализация Real-Time совместного редактирования на сайте (Yjs/Liveblocks)

Совместное редактирование позволяет нескольким пользователям одновременно работать с одним документом, видя изменения друг друга в реальном времени. Google Docs-подобная функциональность для веб-приложений.

Выбор подхода

Yjs — open-source CRDT (Conflict-free Replicated Data Type) библиотека. Самостоятельно управляете сервером (y-websocket или Hocuspocus).

Liveblocks — managed платформа поверх Yjs. Без инфраструктуры, но платно от $0.

Automerge — альтернатива Yjs, от Ink & Switch.

Yjs + Hocuspocus (self-hosted)

Сервер:

npm install @hocuspocus/server @hocuspocus/extension-database
import { Server } from '@hocuspocus/server';
import { Database } from '@hocuspocus/extension-database';
import { Logger } from '@hocuspocus/extension-logger';

const server = Server.configure({
  port: 1234,

  extensions: [
    new Logger(),
    new Database({
      fetch: async ({ documentName }) => {
        // Загрузить документ из БД при первом подключении
        const doc = await documentRepo.findByName(documentName);
        return doc?.content ?? null;  // Yjs binary state
      },
      store: async ({ documentName, state }) => {
        // Сохранить состояние документа в БД
        await documentRepo.upsert(documentName, state);
      }
    })
  ],

  async onAuthenticate({ token, documentName }) {
    // Проверка доступа
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    const canAccess = await checkDocumentAccess(payload.sub, documentName);

    if (!canAccess) {
      throw new Error('Access denied');
    }

    return { userId: payload.sub };
  },

  async onConnect({ documentName, context }) {
    console.log(`User ${context.userId} connected to ${documentName}`);
  }
});

server.listen();

Клиент — Tiptap редактор с Yjs:

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 { HocuspocusProvider } from '@hocuspocus/provider';

function CollaborativeEditor({ documentId }) {
  const doc = useMemo(() => new Y.Doc(), []);

  const provider = useMemo(() => new HocuspocusProvider({
    url: process.env.NEXT_PUBLIC_HOCUSPOCUS_URL,
    name: `doc:${documentId}`,
    document: doc,
    token: getAuthToken(),
    onStatus: ({ status }) => console.log('Provider status:', status)
  }), [documentId, doc]);

  const editor = useEditor({
    extensions: [
      StarterKit.configure({ history: false }),
      Collaboration.configure({ document: doc }),
      CollaborationCursor.configure({
        provider,
        user: {
          name: currentUser.name,
          color: generateUserColor(currentUser.id)
        }
      })
    ]
  });

  return (
    <div className="editor-container">
      <CollaboratorsAvatars provider={provider} />
      <EditorContent editor={editor} />
    </div>
  );
}

// Аватары активных участников
function CollaboratorsAvatars({ provider }) {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const awareness = provider.awareness;

    const updateUsers = () => {
      const states = Array.from(awareness.getStates().values());
      setUsers(states.filter(s => s.user).map(s => s.user));
    };

    awareness.on('change', updateUsers);
    updateUsers();

    return () => awareness.off('change', updateUsers);
  }, [provider]);

  return (
    <div className="collaborators">
      {users.map(user => (
        <Avatar key={user.id} name={user.name}
          color={user.color} title={`${user.name} сейчас редактирует`} />
      ))}
    </div>
  );
}

Liveblocks (managed)

import { createClient } from '@liveblocks/client';
import { createRoomContext } from '@liveblocks/react';
import * as Y from 'yjs';
import { LiveblocksYjsProvider } from '@liveblocks/yjs';

const client = createClient({
  publicApiKey: process.env.NEXT_PUBLIC_LIVEBLOCKS_KEY
});

const { RoomProvider, useRoom } = createRoomContext(client);

function EditorPage({ documentId }) {
  return (
    <RoomProvider id={`document-${documentId}`} initialPresence={{}}>
      <CollaborativeEditorWithLiveblocks />
    </RoomProvider>
  );
}

function CollaborativeEditorWithLiveblocks() {
  const room = useRoom();
  const doc = useMemo(() => new Y.Doc(), []);

  useEffect(() => {
    const provider = new LiveblocksYjsProvider(room, doc);
    return () => provider.destroy();
  }, [room, doc]);

  const editor = useEditor({
    extensions: [
      StarterKit.configure({ history: false }),
      Collaboration.configure({ document: doc })
    ]
  });

  return <EditorContent editor={editor} />;
}

Conflict Resolution через CRDT

Yjs использует CRDT — математически доказанный алгоритм слияния конфликтующих изменений без координации. Два пользователя могут редактировать офлайн, и при синхронизации конфликты разрешаются детерминировано.

Персистентность: y-leveldb / PostgreSQL

// Хранение Yjs-документов в PostgreSQL
const documentTable = `
  CREATE TABLE IF NOT EXISTS documents (
    name VARCHAR(255) PRIMARY KEY,
    content BYTEA NOT NULL,   -- Yjs binary state
    updated_at TIMESTAMPTZ DEFAULT NOW()
  )
`;

// Инкрементальное сохранение через y-protocols
import { encodeStateAsUpdate } from 'yjs';
const binaryState = encodeStateAsUpdate(doc);
await db.query(
  'INSERT INTO documents (name, content) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET content = $2',
  [docName, Buffer.from(binaryState)]
);

Сроки реализации

  • Hocuspocus сервер + Tiptap клиент + базовое collaborative editing — 2–3 недели
  • Курсоры, аватары, персистентность в PostgreSQL — ещё 1 неделя
  • Liveblocks интеграция (без self-hosted сервера) — 1–1.5 недели