Skip to content

Using CRDT & TanStack, Real-time collaborative code editor with rooms, voice chat, and live presence

Notifications You must be signed in to change notification settings

TheAbMehta/livedit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LiveEdit

Real-time collaborative code editor

Built with React 19 Powered by Yjs Monaco Editor Cloudflare Workers TypeScript License:MIT

Write code together in real time. Create a room, share the link, and start collaborating — complete with live cursors, built-in chat, and voice communication.

The Story

I built LiveEdit because pair programming should be easier. Google Docs is great for prose, Monaco editor is great for code, but there's no great "code docs" tool that combines both with real-time collaboration.

In November 2024, I started exploring:

  • Could I build a VS Code-like editor in the browser?
  • Could I make it collaborative without setting up a complex server?
  • Could I deploy it globally with low latency?

The answer was yes, thanks to Yjs CRDTs and Cloudflare Workers. Yjs handles the hard math: guaranteeing that two people can edit simultaneously and converge on the same state. Cloudflare Workers handles the hard infrastructure: running close to users worldwide with automatic scaling on Durable Objects.

This is my exploration into modern collaborative infrastructure. Every part is carefully chosen:

  • React 19 for the UI (it's what I know)
  • Monaco for the editor (it's what VS Code uses)
  • TanStack Start for SSR (for fast first-load)
  • Cloudflare Workers for deployment (for global latency)

I'm not trying to replace Figma Lite or replace GitHub Codespaces. I'm trying to make pair programming just...work. No signup, no setup, just share a link.


LiveEdit Editor

Full IDE-like experience — Monaco editor, file explorer, integrated chat, and real-time collaboration

Table of Contents

Features

Code Collaboration

  • Instant Rooms — Create or join rooms via URL (/room/:id). Each room is a fully isolated collaboration session with its own document state.
  • Conflict-Free Editing — Powered by Yjs CRDTs (Conflict-free Replicated Data Types), multiple users can edit the same file simultaneously without merge conflicts.
  • Monaco Editor — The same editor that powers VS Code — full syntax highlighting, IntelliSense, multi-cursor support, and keyboard shortcuts.
  • Per-File Document Isolation — Each file in the tree is backed by its own Yjs shared text type, so edits to one file never interfere with another.

Real-Time Presence

  • Live Cursors — See exactly where other users are typing with color-coded cursor labels.
  • Selection Awareness — View collaborators' text selections in real time.
  • Follow Mode — Click on a participant to follow their cursor and automatically switch to their active file.
  • Typing Indicators — Know when teammates are actively editing.

Communication

  • Integrated Chat — Built-in room chat synced via Yjs, with markdown support and relative timestamps.
  • Voice Chat — WebRTC peer-to-peer audio with mute/unmute, deafen controls, and real-time speaking indicators.
  • Participant List — See who's in the room, what file they're working on, and their online status.

Developer Experience

  • VS Code-Inspired UI — Activity bar, file explorer, tabbed editor, resizable panels, and a status bar — feels like home.
  • File Explorer — Hierarchical tree view with file type icons and expand/collapse.
  • Keyboard ShortcutsCtrl+B to toggle sidebar, plus all standard Monaco editor shortcuts.
  • Onboarding — First-time welcome modal guides new users through the interface.

Infrastructure

  • Edge Deployment — Runs on Cloudflare Workers globally with <50ms latency to most users.
  • Durable Objects — Server-side session state with 24-hour document cache via Cloudflare Durable Objects.
  • Auto-Reconnect — Exponential backoff WebSocket reconnection with connection status indicators.
  • Guest Authentication — Instant access with auto-generated names and persistent identity via localStorage.

Screenshots

Landing Page & Authentication
Sign In Dashboard
Landing Page Dashboard

Sign in as a guest with an optional display name, or connect with Google OAuth. The dashboard lets you create new rooms or join existing ones by ID, with quick access to recent rooms.

Welcome & Onboarding

Welcome Modal

First-time visitors see an interactive onboarding modal that walks through the core features — file explorer, follow mode, chat, voice, and keyboard shortcuts.

Editor Interface
Empty State With Code
Empty Editor Editor with Code

The editor layout mirrors VS Code with an activity bar, collapsible sidebar, tabbed editor area, and a status bar showing connection status, cursor position, language, encoding, and room name.

Chat & Participants
Integrated Chat Participants Panel
Chat Participants

Chat with your team while editing. The participants panel shows who's online, what file they're working on, and lets you follow their cursor in real time.

Architecture

LiveEdit is split into two independently deployed services on Cloudflare's edge network:

┌─────────────────────────────────────────────────────────┐
│                        Client                           │
│  React 19 + TanStack Start (SSR) + Monaco + Yjs        │
└──────────┬──────────────────────────────────┬───────────┘
           │ HTTPS (SSR)                      │ WebSocket
           ▼                                  ▼
┌────────────────────────┐   ┌────────────────────────────┐
│  SSR App Worker        │   │  Collaboration Worker      │
│  (Cloudflare Workers)  │   │  (Cloudflare Workers)      │
│                        │   │                            │
│  • Server-side         │   │  ┌──────────────────────┐  │
│    rendering           │   │  │  Durable Object      │  │
│  • Static assets       │   │  │  (Session DO)        │  │
│  • Route handling      │   │  │                      │  │
│                        │   │  │  • Yjs sync protocol │  │
│                        │   │  │  • WebSocket mgmt    │  │
│  wrangler.toml         │   │  │  • 24h doc cache     │  │
│                        │   │  │  • Awareness relay    │  │
│                        │   │  └──────────────────────┘  │
│                        │   │                            │
│                        │   │  wrangler.collaboration.   │
│                        │   │  toml                      │
└────────────────────────┘   └────────────────────────────┘

How Collaboration Works

  1. User joins a room — Client connects via WebSocket to the Collaboration Worker
  2. Durable Object routes connections to the same session by room ID
  3. Yjs sync protocol handles initial state sync and incremental updates
  4. Awareness protocol broadcasts cursor positions, selections, and user metadata
  5. Voice chat uses WebRTC with signaling over the Yjs awareness channel

Why CRDTs?

Traditional operational transform (OT) systems require a central server to resolve conflicts. Yjs CRDTs resolve conflicts mathematically at each client, enabling:

  • Offline editing — Changes merge cleanly when reconnected
  • Low latency — No round-trip to a central authority needed
  • Peer-to-peer ready — Can work without a server (via WebRTC)
  • Guaranteed convergence — All clients reach the same state eventually

Tech Stack

Layer Technology Purpose
Framework React 19 + TanStack Start SSR-capable React with file-based routing
Editor Monaco Editor VS Code's editor component
Collaboration Yjs CRDT framework for real-time sync
Voice WebRTC Peer-to-peer audio communication
Styling Tailwind CSS + shadcn/ui Utility-first CSS with accessible components
UI Components Radix UI + Lucide Icons Headless accessible primitives
Virtualization TanStack Virtual Efficient rendering for chat messages
Panels react-resizable-panels Draggable, collapsible panel layout
Runtime Cloudflare Workers Edge-deployed serverless functions
State Durable Objects Strongly consistent session storage
Build Vite 7 Fast HMR and optimized production builds
Testing Vitest + Playwright Unit and end-to-end testing
Language TypeScript 5.7 Full type safety across the stack

Getting Started

Prerequisites

Installation

# Clone the repository
git clone https://github.com/your-username/livedit-app.git
cd livedit-app

# Install dependencies
bun install

# Copy environment variables
cp .env.example .env.local

Local Development

bun run dev

The app will be available at http://localhost:3000.

Building for Production

bun run build

Project Structure

livedit-app/
├── src/
│   ├── routes/                    # TanStack Router (file-based)
│   │   ├── __root.tsx             # Root layout with providers
│   │   ├── index.tsx              # Landing page (/)
│   │   └── room/
│   │       └── $roomId.tsx        # Room page (/room/:id)
│   ├── components/
│   │   ├── layout/
│   │   │   ├── editor-layout.tsx  # Main IDE layout with panels
│   │   │   ├── activity-bar.tsx   # VS Code-style sidebar icons
│   │   │   ├── status-bar.tsx     # Bottom status bar
│   │   │   └── loading-skeleton.tsx
│   │   ├── editor/
│   │   │   └── monaco-editor-with-presence.tsx
│   │   ├── file-tree/             # Hierarchical file explorer
│   │   ├── chat/                  # Chat + voice controls
│   │   ├── presence/              # Cursors, user list, indicators
│   │   ├── onboarding/            # Welcome modal
│   │   └── ui/                    # shadcn/ui primitives
│   ├── lib/
│   │   ├── yjs-doc.ts             # Shared Yjs document & awareness
│   │   ├── websocket-provider.ts  # Custom WebSocket provider
│   │   ├── voice-chat.ts          # WebRTC voice implementation
│   │   ├── auth-context.tsx       # Authentication state
│   │   ├── room-context.tsx       # Room providers & sync setup
│   │   └── use-presence.ts        # Presence hook
│   ├── hooks/                     # React hooks
│   └── types/                     # TypeScript type definitions
├── workers/
│   ├── collaboration.ts           # Collaboration worker entry
│   └── durable-objects/
│       └── session.ts             # SessionDurableObject (Yjs sync)
├── tests/                         # E2E tests (Playwright)
├── scripts/
│   ├── deploy-worker.sh           # Deploy collaboration worker
│   ├── deploy-app.sh              # Deploy SSR app
│   ├── deploy-all.sh              # Deploy everything
│   └── smoke-test.sh              # Production smoke tests
├── docs/screenshots/              # README screenshots
├── wrangler.toml                  # SSR app config
├── wrangler.collaboration.toml    # Collaboration worker config
└── DEPLOYMENT.md                  # Detailed deployment guide

Deployment

LiveEdit is deployed on Cloudflare's edge infrastructure across 300+ locations worldwide.

Quick Deploy

# Authenticate with Cloudflare
bunx wrangler login

# Deploy both workers
./scripts/deploy-all.sh production

Individual Deployments

# Deploy collaboration worker only
./scripts/deploy-worker.sh production

# Deploy SSR app only
./scripts/deploy-app.sh production

# Run smoke tests against production
./scripts/smoke-test.sh production

Environment Configuration

Variable Location Description
ENVIRONMENT wrangler.toml production or staging
WORKER_URL wrangler.toml Collaboration worker WebSocket URL
ALLOWED_ORIGINS wrangler.collaboration.toml CORS allowed origins

Monitoring

# Stream real-time logs from SSR worker
bunx wrangler tail --env production

# Stream logs from collaboration worker
bunx wrangler tail -c wrangler.collaboration.toml --env production

Dashboard available at dash.cloudflare.com/workers.

Testing

Unit Tests

# Run all unit tests
bun run test

# Run with interactive UI
bun run test:ui

End-to-End Tests

# Run Playwright E2E tests
bun run test:e2e

Linting

bun run eslint

Key Design Decisions

Decision Rationale
Yjs over OT CRDTs provide mathematical convergence guarantees without a central authority. Yjs has the best JavaScript ecosystem support.
Custom WebSocket provider The built-in y-websocket provider didn't support Cloudflare Workers. Custom provider adds auto-reconnect with exponential backoff.
Durable Objects Need co-located state (WebSocket connections + Yjs docs) in a single actor. Durable Objects provide exactly this with strong consistency.
Monaco over CodeMirror Monaco provides the closest experience to VS Code. The y-monaco binding offers seamless Yjs integration.
WebRTC for voice Peer-to-peer audio avoids the cost and latency of a media server. Awareness channel for signaling eliminates the need for a separate STUN/TURN server for small rooms.
TanStack Start SSR with Cloudflare Workers support, file-based routing, and an active ecosystem. First-load performance is critical for collaboration tools.

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Make your changes
  4. Test: bun run test && bun run test:e2e
  5. Lint: bun run eslint
  6. Submit a pull request

Please ensure all tests pass and the build succeeds before submitting.

License

This project is licensed under the MIT License — see the LICENSE file for details.


Built with Yjs + Monaco on Cloudflare Workers