ByteGuideHub
Real-Time Collaboration • • 9 min read

Building a Self-Hosted Real-Time Collaborative CAD Platform with CRDTs and WebAssembly

A practical guide to architecting a self-hosted, browser-based CAD platform with real-time multi-user editing, CRDT-based conflict resolution, and high-performance geometry processing using WebAssembly.

Sponsored / Ad Space

Building a Self-Hosted Real-Time Collaborative CAD Platform with CRDTs and WebAssembly

Quick Summary (TL;DR)

  • Goal: Build a self-hosted, browser-based CAD platform where multiple users edit geometry in real time.
  • Core stack: Yjs (CRDTs) for conflict-free document sync + WebAssembly (Rust/C++) for heavy geometry math + a WebSocket relay server (Node.js) for real-time transport.
  • Why self-host: Full data sovereignty, no per-seat SaaS fees, and deep customization of the geometry kernel.
  • Key insight: CRDTs handle concurrent edits; WebAssembly handles compute; WebSockets handle transport. Keep these three concerns separate and the system stays maintainable.

Real-time collaborative CAD is one of the hardest distributed-systems problems you can pick: geometry must stay mathematically consistent, latency must stay imperceptible, and users expect the tool to survive flaky networks. This guide walks through a production-ready architecture you can host yourself, using CRDTs for conflict resolution and WebAssembly for near-native geometry performance.

Why Build a Self-Hosted Collaborative CAD Platform?

Commercial CAD tools lock your design data inside proprietary clouds and charge per seat. Self-hosting changes the calculus:

  • Data sovereignty — proprietary mechanical designs never leave your infrastructure.
  • Cost predictability — a single VPS handles dozens of concurrent editors; no per-user licensing.
  • Extensibility — you control the geometry kernel, the CRDT schema, and the plugin surface.
  • Compliance — meet ITAR, HIPAA, or internal data-residency rules without vendor negotiations.

The trade-off is operational responsibility: you own uptime, backups, and security. For teams with sensitive IP, that’s usually a trade worth making.

Core Architecture Overview

The system splits cleanly into a thin client, a stateless relay, and a compute layer that lives entirely in the browser.

flowchart TD
    subgraph Browser["Browser Client"]
        UI[Canvas / WebGL UI]
        CRDT[Yjs CRDT Document]
        WASM[Wasm Geometry Kernel]
        UI <--> CRDT
        CRDT --> WASM
        WASM --> UI
    end
    subgraph Server["Self-Hosted Server"]
        WS[WebSocket Relay]
        AUTH[Auth / ACL]
        PERSIST[(Postgres / S3)]
        WS --> AUTH
        WS --> PERSIST
    end
    CRDT <-->|Yjs updates| WS
    WS <-->|updates| CRDT2[Peer CRDT]

Client-Side Components

  • CRDT document — the single source of truth for all entities, constraints, and metadata.
  • Wasm geometry kernel — tessellation, boolean operations, constraint solving.
  • Renderer — WebGL/WebGPU canvas that draws the resolved geometry.
  • Sync adapter — serializes CRDT updates over WebSocket.

Server-Side Components

  • WebSocket relay — broadcasts binary CRDT updates; holds no geometry state.
  • Auth/ACL service — validates JWTs and enforces per-document permissions.
  • Persistence — periodic snapshots of the CRDT state to Postgres or object storage.

The server is deliberately stateless with respect to geometry. It never parses CAD primitives—it just relays opaque CRDT updates. This keeps the relay cheap and horizontally scalable.

Choosing the Right CRDT for CAD Data

CRDT Libraries Comparison

LibraryData ModelOffline SupportBinary EncodingBest For
YjsShared types (Map, Array, Text)✅ Excellent✅ CompactGeneral CAD documents, cursors
AutomergeJSON-like documents✅ Excellent✅ (columnar)Document-centric apps
LoroRich, movable containers✅ Excellent✅ FastHigh-perf structured data
CustomTailored to primitives⚠️ Manual✅ OptimalSpecialized geometry kernels

Recommendation: Start with Yjs. Its Y.Map/Y.Array primitives map naturally onto CAD entity lists, it has mature awareness/presence support, and its binary encoding is compact enough for high-frequency updates.

Modeling CAD Entities with CRDTs

Model each entity as a stable-ID entry in a Y.Map:

const doc = new Y.Doc();
const entities = doc.getMap('entities');

// Each entity is itself a Y.Map keyed by a UUID
function addLine(id, start, end) {
  const line = new Y.Map();
  line.set('type', 'line');
  line.set('start', new Y.Map([['x', start.x], ['y', start.y]]));
  line.set('end',   new Y.Map([['x', end.x],   ['y', end.y]]));
  entities.set(id, line);
}

Because each coordinate is its own CRDT cell, two users can drag different endpoints of the same line simultaneously without clobbering each other.

High-Performance Geometry with WebAssembly

What to Offload to WebAssembly

OperationJS FeasibilityWasm Payoff
Constraint solving❌ Too slow🔥🔥🔥 Critical
Boolean (CSG) ops❌ Too slow🔥🔥🔥 Critical
Tessellation / meshing⚠️ Marginal🔥🔥 High
Hit-testing✅ Fine🔥 Low
UI layout✅ Fine—

Compile a C++ or Rust geometry kernel (e.g., adapted OpenCASCADE or a custom solver) to wasm32-unknown-unknown.

Integrating WebAssembly with JavaScript

import init, { solve_constraints, tessellate } from './cad_kernel.js';

await init();
const mesh = tessellate(JSON.stringify(entitiesAsPlainObject()));

Critical rule: never pass the live CRDT document into Wasm. Serialize a plain snapshot, run the kernel, then diff the result back into the CRDT. This keeps the Wasm boundary clean and avoids aliasing bugs.

Real-Time Synchronization and Conflict Resolution

WebSocket Protocol Design

Use a minimal binary frame: [1-byte type][4-byte docId][payload].

TypeDirectionPayload
0x01 UPDATEclient → server → clientsYjs update
0x02 SYNC_REQclient → serverstate vector
0x03 AWARENESSclient ↔ clientcursors, presence
0x04 SNAPSHOTserver → clientfull state

Handling Offline Edits and Merging

CRDTs make offline editing trivial: edits apply locally, queue in the update log, and merge on reconnect via state-vector exchange. The merge is deterministic—no “last write wins” data loss.

sequenceDiagram
    participant A as Client A (offline)
    participant S as Server
    participant B as Client B
    A->>A: Apply edits locally
    B->>S: Send updates
    S->>B: Broadcast
    A->>S: Reconnect + state vector
    S->>A: Missing updates
    A->>S: A's queued updates
    S->>B: Broadcast A's updates

Step-by-Step Implementation Guide

Step 1: Set Up the Project Structure

cad-platform/
├── client/          # Vite + React + Yjs
├── kernel/          # Rust geometry → Wasm
├── server/          # Node.js WebSocket relay
└── docker-compose.yml

Step 2: Define the CRDT Document Schema

Structure the doc into entities, constraints, and meta maps. Reserve meta for document-level settings and use stable UUIDs everywhere.

Step 3: Implement the WebSocket Server

import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
const docs = new Map(); // docId -> Set<ws>

wss.on('connection', (ws, req) => {
  const docId = new URL(req.url, 'http://x').searchParams.get('doc');
  if (!docs.has(docId)) docs.set(docId, new Set());
  docs.get(docId).add(ws);
  ws.on('message', (msg) => {
    for (const peer of docs.get(docId)) {
      if (peer !== ws && peer.readyState === 1) peer.send(msg);
    }
  });
  ws.on('close', () => docs.get(docId)?.delete(ws));
});

Step 4: Integrate WebAssembly Geometry Engine

Build with wasm-pack build --target web, then load the kernel lazily on first geometry operation to keep initial page load fast.

Step 5: Build the Collaborative UI

Render from the CRDT, not from local component state. Subscribe to doc.on('update', rerender) and debounce re-tessellation with requestIdleCallback.

Step 6: Add Presence and Cursors

Use Yjs awareness to broadcast { userId, cursor, selection }. Render remote cursors from awareness state—they’re ephemeral and never persisted.

Step 7: Test and Deploy

Simulate network partitions with toxiproxy. Deploy with Docker Compose: Nginx (TLS + static) → Node relay → Postgres. Enable WSS and sticky sessions if you scale horizontally.

Performance Optimization Tips

  • Debounce tessellation — run the Wasm kernel at ~30 Hz, not on every keystroke.
  • Delta compression — Yjs updates are already compact; batch them every 16 ms.
  • Virtualize large documents — only tessellate entities in the viewport.
  • Transferable ArrayBuffers — pass binary updates without copying.
  • Snapshot compaction — periodically collapse the update log server-side.

Security and Self-Hosting Considerations

  • Auth: validate JWTs at the WebSocket handshake; reject unauthenticated upgrades.
  • ACL: enforce per-document read/write roles server-side—never trust the client.
  • TLS everywhere: use wss:// and HSTS.
  • Rate limiting: cap update frequency per connection to prevent abuse.
  • Backups: snapshot CRDT state to object storage on a schedule.

Conclusion

A self-hosted collaborative CAD platform is achievable with three well-chosen pillars: Yjs for conflict-free sync, WebAssembly for geometry performance, and a stateless WebSocket relay for transport. Start with the relay and CRDT schema, add the Wasm kernel second, and layer presence and offline support last. The result is a fast, private, and fully extensible design tool your team actually owns.

FAQ

What is a CRDT and why use it for collaborative CAD? A CRDT (Conflict-free Replicated Data Type) lets multiple users edit the same document concurrently without central locking, automatically resolving conflicts. For CAD, CRDTs keep geometric entities consistent across all clients—even during offline edits or network partitions.

How does WebAssembly improve CAD performance in the browser? Wasm delivers near-native speed for geometry-heavy tasks like constraint solving, CSG booleans, and meshing. Compiling a C++ or Rust kernel to Wasm lets you run complex operations locally without server round-trips.

Can I self-host a collaborative CAD platform? Yes. Self-hosting gives you full control over data privacy, customization, and cost. Deploy the WebSocket relay and static assets on your own VPS, Docker, or Kubernetes cluster—the architecture here is designed for exactly that.

Which CRDT library is best for CAD applications? Yjs is the strongest default thanks to its maturity, compact binary encoding, and rich awareness/presence ecosystem. Automerge and Loro are solid alternatives, but for custom geometry primitives you may still need to extend the schema.

How do I handle offline editing? CRDTs support it natively. Each client keeps a local replica, applies edits offline, and queues updates. On reconnect, a state-vector exchange merges changes deterministically—no data loss, no manual conflict resolution.

Sponsored / Ad Space