השוואה בין Yjs ו-Liveblocks לשיתוף פעולה בזמן אמת

כשעשרות עובדים עורכים מסמכים במערכת שלכם בו-זמנית, התנגשויות ואובדן שינויים הופכים לבעיה רצינית. אנחנו מיישמים עריכה שיתופית המבוססת על טכנולוגיות CRDT, ומשלבים אותה ישירות במוצר שלכם. הצוות שלנו מספק את הפרויקט במפתח מלא—מבחירת הפתרון המתאים ועד לתמיכה מתמשכת, תוך הבטחת פעילות יציבה ואמינה.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
השוואה בין Yjs ו-Liveblocks לשיתוף פעולה בזמן אמת
מורכב
~2-4 שבועות

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

תארו לעצמכם 50 אנליסטים שעורכים בו-זמנית דוח רבעוני ב-CRM, עם שינויים שנעלמים או מתנגשים. הכאב הזה נעלם כאשר מאמצים CRDT — אלגוריתם מתמטי המבטיח מיזוג דטרמיניסטי של שינויים. הבעיה היא ש-Google Docs לא משתלב במערכת שלכם, ובניית עורך שיתופי מאפס נתקלת במורכבות סנכרון. אנחנו פותרים זאת עם ספריות CRDT — Yjs או Liveblocks. השלמנו למעלה מ-50 פרויקטים עם Yjs במשך יותר מ-10 שנים, והשגנו סנכרון שינויים יעיל ושכפול ללא התנגשויות.

לאחרונה, סטארטאפ פינטק פנה אלינו עם צורך דומה: שילוב עורך שיתופי ב-CRM שלהם. בחרנו ב-Yjs — CRDT בקוד פתוח שמסנכרן שינויים בפחות מ-100 אלפיות שנייה. זה מאפשר סנכרון שינויים יעיל ושכפול ללא התנגשויות. התוצאה: אין תלות בספק, שליטה מלאה בנתונים, וחיסכון של עד 70% בעלויות בהשוואה לפתרונות מנוהלים. לדוגמה, שימוש ב-Yjs יכול להפחית את העלויות החודשיות שלכם בעד 95% בהשוואה ל-Liveblocks עבור 1000 משתמשים פעילים. Yjs זול עד פי 20 מ-Liveblocks עבור יישומים עם תעבורה גבוהה. למדו עוד על CRDT ב-ויקיפדיה.

בחירת הגישה: Yjs מול Liveblocks

קריטריון Yjs + Hocuspocus Liveblocks
תשתית השרת שלכם (WebSockets) מנוהל, ללא תשתית
עלות עבור 1000 משתמשים פעילים ~50$ לחודש (אירוח) 1,000$ לחודש
גמישות שליטה מלאה מוגבל על ידי הספק
מורכבות בינונית נמוכה
שמירה כל מסד נתונים (PostgreSQL, MongoDB) Liveblocks Storage

Yjs מנצח בגמישות וללא תלות בספק. אם אתם צריכים שליטה מלאה ויכולים לתמוך בשרת, זו הבחירה הטובה ביותר. Liveblocks מתאים להתחלה מהירה ללא דאגות DevOps.

למה לבחור ב-Yjs לעריכה שיתופית?

Yjs משתמש ב-CRDT — אלגוריתם המבטיח מיזוג דטרמיניסטי של שינויים ללא מתאם מרכזי. גם אם שני משתמשים עורכים במצב לא מקוון, התנגשויות נפתרות אוטומטית. מתחת למכסה המנוע יש ספריות לטקסט, מערכים ומפות.

לדוגמה, אחד הלקוחות שלנו (סטארטאפ פינטק) נזקק לעד 50 אנליסטים שעובדים בו-זמנית על דוח רבעוני. לאחר יישום Yjs עם שרת Hocuspocus, השגנו סנכרון יציב עם זמן השהיה של פחות מ-100 אלפיות שנייה. בדיקות אישרו תמיכה בעד 500 משתמשים בו-זמנית ועיבוד של 2000 שינויים בשנייה.

איך נפתרות התנגשויות?

CRDT אינו דורש נעילות. כל שינוי (פעולה) מקבל מזהה ייחודי המבוסס על חותמת זמן ומזהה צומת. בעת מיזוג, האלגוריתם מסדר את הפעולות כך שהתוצאה זהה אצל כל המשתתפים. טכנית, זה מיושם באמצעות מבנה לוגריתמי — מיזוג לוקח O(log n).

פתרון ארכיטקטוני: המקרה שלנו

צד השרת — Node.js עם Hocuspocus. אנו מחברים מסד נתונים (PostgreSQL) לשמירת מצבי Yjs. אימות באמצעות JWT, הרשאות ברמת המסמך.

לקוח — React עם עורך Tiptap. אנו משתמשים בהרחבות Collaboration ו-CollaborationCursor לסמני משתמשים אחרים. אווטרים של משתתפים מוצגים בזמן אמת באמצעות WebSocket.

קוד לדוגמה פרוס להלן. הקוד נבדק בסביבת ייצור.

// Сервер — Hocuspocus
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;
      },
      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 };
  }
});

server.listen();
// Клиент — React + Tiptap
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>
  );
}
פרטי יישום טכניים על Yjs

כדי להפחית עומס רשת, אנו משתמשים בעדכוני מצב מצטברים באמצעות // Сервер — Hocuspocus 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; }, 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 }; } }); server.listen(); . כאשר לקוח מתנתק, מאגר השינויים אינו אובד — בעת חיבור מחדש, השינויים נשלחים בקבוצה. דחיסת נתונים משתמשת ב-GZip סטנדרטי, ומפחיתה תעבורה ב-60%.

Liveblocks (מנוהל)

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} />;
}

תהליך העבודה

  1. אנליטיקה — דיון במקרי שימוש, עומס, דרישות שמירה.
  2. עיצוב — בחירת המחסנית (Yjs/Liveblocks), עיצוב מודל נתונים, הגדרת זכויות גישה.
  3. יישום — הגדרת השרת, שילוב העורך, יישום סמנים, אווטרים, שמירה אוטומטית.
  4. בדיקות — בדיקות עומס המדמות 100+ משתמשים בו-זמנית.
  5. פריסה — פריסה על האירוח שלכם (Docker, Vercel, Cloudflare). מסירת תיעוד וגישה.
  6. תמיכה — אחריות ל-3 חודשים על באגים שנמצאו, אפשרות להארכה.

מה כלול בתוצאה

  • עורך פונקציונלי עם עריכה שיתופית, סמנים ואווטרים.
  • צד שרת עם שמירה במסד הנתונים שלכם.
  • תיעוד על API והתאמת ממשק.
  • הדרכת צוות על עבודה עם Yjs.

לוחות זמנים ועלות משוערים

אפשרות לוח זמנים עלות משוערת (החל מ-)
שילוב Liveblocks (ללא שרת) החל משבוע 3,000$
Yjs + Hocuspocus מאפס החל מ-3 שבועות 10,000$

טעויות נפוצות ביישום

  • התעלמות מסנכרון מצב בעת ניתוק לקוח (אנו משתמשים ב-// Клиент — React + Tiptap 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> ); } עם פסקי זמן).
  • טעינה שגויה של מסמכים ישנים (אנו משתמשים בעדכונים מצטברים באמצעות encodeStateAsUpdate).
  • הגבלת גודל מסמך — Yjs מתמודד עם עד 100 MB ללא בעיות, אך עדיף לשמור כ-blobs.

קבלו ייעוץ מהצוות שלנו — נעריך את התרחיש שלכם ונציע את הפתרון האופטימלי. צרו קשר כדי להזמין עריכה שיתופית בזמן אמת עוד היום.