Skip to main content
FA
Faiz Akram
HomeAboutExpertiseProjectsBlogContact
FA
Faiz Akram

Senior Technical Architect specializing in enterprise-grade solutions, cloud architecture, and modern development practices.

Quick Links

Privacy PolicyTerms of ServiceBlog

Connect

© 2026 Faiz Akram. All rights reserved.

Back to Blog
Building Real-Time Collaborative Applications with CRDTs and WebSockets
Full-Stack

Building Real-Time Collaborative Applications with CRDTs and WebSockets

F
Faiz Akram
August 13, 2026
7 min read

Real-time collaborative applications, like Google Docs and Figma, have become critical in 2024 as teams demand seamless, low-latency editing across devices and geographies. Achieving robust collaboration—without data loss or conflicts—requires more than just WebSockets and naive state broadcasting. In this post, I’ll show how to architect, build, and operate production-ready real-time collaborative apps using Conflict-Free Replicated Data Types (CRDTs), WebSockets, and modern full-stack frameworks.

What Are CRDTs and Why Do They Matter for Real-Time Collaboration?

A CRDT (Conflict-Free Replicated Data Type) is a data structure that automatically resolves conflicts in distributed systems, allowing multiple users to edit shared data simultaneously without central coordination. CRDTs guarantee strong eventual consistency: all replicas converge to the same state, even with out-of-order updates or network partitions.

In a collaborative text editor, for example, each user’s changes are represented as operations (insert, delete) on a CRDT list structure. These operations are broadcast to peers via WebSockets. When network conditions cause edits to arrive out of order, the CRDT logic ensures the final document is still correct, with no manual merge logic required.

Here’s an example using the Yjs CRDT library (v13.5.43) in a Node.js WebSocket server:

// server.js (Node.js 18+, Yjs 13.5.43, ws 8.13.0)
const http = require('http');
const WebSocket = require('ws');
const Y = require('yjs');
const { encodeStateAsUpdate, applyUpdate } = Y;

const doc = new Y.Doc();
const server = http.createServer();
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
  // Send current doc state to new client
  ws.send(encodeStateAsUpdate(doc));
  ws.on('message', (message) => {
    applyUpdate(doc, new Uint8Array(message));
    // Broadcast change to all clients
    wss.clients.forEach(client => {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});

server.listen(3000);

Key insight: CRDTs like Yjs allow you to build merge-free, distributed editing where every client is always eventually consistent—no matter the network conditions.

Step 1: Designing Your Data Model for CRDT Backing

Why Traditional Models Fail Under Concurrency

Relational and document databases (e.g., PostgreSQL, MongoDB) weren't designed for concurrent, decentralized edits. They rely on locking or centralized conflict resolution, both of which break down in real-time, high-latency scenarios. In 2024, users expect sub-100ms feedback even across continents.

How to Model Collaborative State with CRDTs

Define your shared state (e.g., text, lists, trees) using CRDT-backed data structures. For a collaborative whiteboard:

  • Use a Y.Map for top-level entities (e.g., shapes by ID)
  • Use Y.Array for ordered collections (e.g., points in a polyline)
  • Use Y.Text for rich text objects

Yjs and Automerge (v2.0.1) both support these CRDT types. Yjs offers better performance for large, multi-user docs, while Automerge is easier to integrate with React (via automerge-repo).

// Example: Yjs data structure for a collaborative whiteboard
const whiteboard = doc.getMap('shapes');
whiteboard.set('shape-1', new Y.Map({ type: 'rect', x: 10, y: 20 }));

Key insight: Model every collaborative object as a CRDT data structure to unlock true concurrent, offline-tolerant editing.

Step 2: Real-Time Sync via WebSockets and Broadcast Channels

Setting Up WebSocket Infrastructure

WebSockets (RFC 6455) provide low-latency, bidirectional communication ideal for real-time collaboration. For full-stack Node.js apps, I recommend the ws (v8.13.0) library. In production, always terminate WebSockets behind a reverse proxy supporting sticky sessions (e.g., NGINX, ALB with AWS Lambda, or Cloudflare Workers for edge presence).

Example NGINX config for proxying WebSockets:

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}
server {
  listen 443 ssl;
  server_name collab.example.com;

  location /ws/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

Peer-to-Peer Sync for Edge/Offline

For global scale or offline-first, combine WebSocket server with peer-to-peer (P2P) sync using WebRTC and the BroadcastChannel API (supported in all major browsers as of Chrome 120+). Yjs supports this out-of-the-box with y-webrtc (v10.0.10).

Key insight: Hybrid WebSocket + P2P sync lets you serve low-latency collaboration at global scale, even when users go offline.

Step 3: Persisting and Versioning State Securely

Why You Can’t Trust Client-Only State

Real-time CRDT state is ephemeral and memory-resident. Without server-side persistence, users lose data on disconnect, and you can’t audit or roll back changes. In 2024, compliance (e.g., SOC2, GDPR) demands audit logs and recovery.

How to Persist CRDT State

Periodically serialize the CRDT document and write it to durable storage. For Yjs, use the y-leveldb (v1.2.0) adapter for server-side persistence, or y-dynamodb (if on AWS). For multi-tenant SaaS, use a composite key per document and user. Retain deltas (updates) for conflict resolution and auditing:

// Persist Yjs doc state every 10 seconds
doc.on('update', (update) => {
  persistUpdateToDynamoDB(docId, update);
});

For versioning, store snapshots every N minutes and all deltas since last snapshot (pattern: Event Sourcing).

Key insight: Always persist CRDT state and update logs to a durable backend for reliability, auditing, and recovery.

Step 4: Integrating with Modern Frontends (React, Vue, Svelte)

Wiring Up CRDTs to UI State

CRDTs emit granular events (insert, delete, update) you can bind directly to React state (via useEffect), or to Vue's reactivity API. The yjs-react (v2.0.1) and automerge-repo-react packages provide idiomatic bindings.

Example: Collaborative text editor in React (Yjs + yjs-react):

import { useYText } from 'yjs-react';
const [text, setText] = useYText(doc.getText('editor'));
<input value={text} onChange={e => setText(e.target.value)} />

Handling User Presence and Cursors

For UX parity with Google Docs, track user cursors and presence using a Y.Map or Automerge map per user ID, synced via CRDT updates.

Key insight: Use CRDT-reactive bindings for instant UI updates and presence indicators, minimizing manual state management.

Step 5: Scaling and Securing Collaborative Backends

Horizontal Scaling with Stateless WebSocket Servers

WebSockets typically require sticky sessions, but CRDT updates are small (often <1KB per op). In production, use Redis Pub/Sub (v7+) or AWS ElastiCache to fan out updates between stateless WebSocket servers.

Example Redis setup for multi-instance sync:

const redis = require('redis').createClient();
wss.on('connection', (ws) => {
  redis.subscribe('crdt-updates');
  ws.on('message', (msg) => {
    redis.publish('crdt-updates', msg);
  });
  redis.on('message', (_, msg) => {
    ws.send(msg);
  });
});

Authentication and Authorization

Authenticate users via JWT (with short TTL) on connection. Authorize CRDT operations server-side—never trust client metadata. For OAuth2 in Node.js, use the oidc-provider (v7.15.1) library.

Key insight: Stateless backends plus centralized pub/sub enable global scale and resilience for collaborative workloads.

Tool Comparison: CRDT Libraries and Collaboration Backends

Tool / ServiceStrengthsLimitationsProduction Notes
Yjs (v13.5.43)Fast, binary-encoded, rich ecosystemSteeper learning curveBest for large docs, P2P
Automerge (v2.0.1)Simple API, React-friendly, JSON-basedSlower, larger network payloadsGood for small/medium docs
LiveblocksManaged backend, out-of-box presence/cursorUsage-based pricing, closed sourceFastest to production
Fluid FrameworkEnterprise features, MS ecosystemComplex setup, heavy dependenciesBest for Office365/Teams
Firebase Realtime DBEasy setup, presence, websocketsNo CRDT, manual conflict resolutionNot suitable for complex merges
Supabase RealtimePostgres-based, open sourceNo native CRDT, limited scalingGreat for simple use cases

Key insight: Yjs and Automerge dominate open-source CRDT collaboration; Liveblocks and Fluid fit managed/enterprise needs, while Firebase lacks true CRDT semantics.

Frequently Asked Questions

Q: What is a CRDT and why is it better than OT (Operational Transformation)? A: A CRDT (Conflict-Free Replicated Data Type) is a distributed data structure that ensures all replicas converge to the same state without central coordination. Unlike OT (used by Google Docs), CRDTs are easier to reason about, support true peer-to-peer sync, and are more robust under network partitions.

Q: How do I scale WebSocket servers for global collaboration? A: Use stateless WebSocket servers behind a load balancer, with Redis Pub/Sub or AWS ElastiCache to synchronize CRDT updates between instances. For global edge presence, deploy via Cloudflare Workers or AWS Global Accelerator.

Q: Can I persist collaborative state in a traditional SQL or NoSQL database? A: Yes, but you should serialize and persist the binary CRDT state (and operation logs) rather than try to map CRDT data structures directly to tables or documents. Use adapters like y-leveldb, y-dynamodb, or blob storage.

Key Takeaways

  • Always use CRDT-backed data models for real-time, multi-user collaborative apps to avoid merge conflicts and support offline editing.
  • Combine WebSockets (for low-latency server sync) with BroadcastChannel/WebRTC (for P2P and edge performance) in your real-time pipeline.
  • Persist CRDT state and deltas to durable storage for resilience, auditability, and disaster recovery.
  • Use stateless WebSocket backends with Redis/AWS Pub/Sub to scale collaboration horizontally.
  • Integrate CRDTs with frontend frameworks via reactive bindings (yjs-react, automerge-repo-react) for seamless UI updates and presence.
  • Yjs (open source) is the most production-proven CRDT library for high-scale, complex documents; Liveblocks and Fluid Framework lead in managed and enterprise scenarios.

Tags

real-timefull-stackCRDTwebsocketscollaborative apps

Share this article

Found it helpful? Share it with your network.

X / TwitterLinkedInFacebookWhatsApp

Related Articles

More on Full-Stack and related topics

Full-Stack Observability: Modern Patterns, Tools, and Real-World Setups
Full-Stack
August 5, 2026
5 min read

Full-Stack Observability: Modern Patterns, Tools, and Real-World Setups

Master full-stack observability in 2024: end-to-end tracing, metrics, and logs for cloud-native apps. Key tools, best patterns, and step-by-step production configs.

cloudfull-stackobservability
Read More
Modernizing Full-Stack Authentication with OAuth2, OIDC, and PKCE
Full-Stack
July 29, 2026
6 min read

Modernizing Full-Stack Authentication with OAuth2, OIDC, and PKCE

Learn how to implement secure, production-grade authentication in full-stack apps using OAuth2, OIDC, and PKCE. Step-by-step Node.js and React guide.

authenticationoauth2openid-connect
Read More
Advanced Angular Patterns: RxJS, State Management & Performance
Full-Stack
November 20, 2024
6 min read

Advanced Angular Patterns: RxJS, State Management & Performance

Unlock advanced Angular patterns for 2024: RxJS mastery, state management with NgRx, and real-world performance gains for scalable, production-ready apps.

AngularRxJSNgRx
Read More