Back to Blog
·11 min

How to Build a Real-Time Collaborative Editor

Real-time collaboration is one of those features that separates good apps from great ones. When multiple users can edit the same document simultaneously and see each other's cursors, the experience feels magical.

In this guide, we will build a collaborative text editor using Next.js, WebSockets, and Operational Transform (OT). Users can edit documents together, see live cursors, leave comments, and track version history.

Architecture Overview

A collaborative editor needs three things:

  • A text editor component with cursor tracking
  • A WebSocket server for real-time communication
  • An Operational Transform engine to resolve concurrent edits

Setting Up WebSocket Connection

'use client'

import { useState, useEffect, useCallback } from 'react'

export function CollaborativeEditor({ documentId }: { documentId: string }) {
  const [content, setContent] = useState('')
  const [cursors, setCursors] = useState<Record<string, { x: number; y: number }>>({})
  const [ws, setWs] = useState<WebSocket | null>(null)

  useEffect(() => {
    const socket = new WebSocket(`wss://api.example.com/ws?doc=${documentId}`)
    socket.onmessage = (event) => {
      const data = JSON.parse(event.data)
      if (data.type === 'content') setContent(data.content)
      if (data.type === 'cursor') setCursors(prev => ({ ...prev, [data.userId]: data.position }))
    }
    setWs(socket)
    return () => socket.close()
  }, [documentId])

  const handleChange = useCallback((newContent: string) => {
    setContent(newContent)
    ws?.send(JSON.stringify({ type: 'edit', content: newContent }))
  }, [ws])

  const handleCursorMove = useCallback((position: { x: number; y: number }) => {
    ws?.send(JSON.stringify({ type: 'cursor', position }))
  }, [ws])

  return (
    <div className="relative">
      <textarea
        value={content}
        onChange={e => handleChange(e.target.value)}
        onMouseMove={e => handleCursorMove({ x: e.clientX, y: e.clientY })}
        className="w-full h-[500px] rounded-xl bg-gray-900 p-4 text-sm text-white font-mono border border-gray-700"
      />
      {Object.entries(cursors).map(([userId, pos]) => (
        <div key={userId} style={{ position: 'absolute', left: pos.x, top: pos.y }}>
          <div className="w-0.5 h-5 bg-brand-400" />
          <span className="text-xs text-brand-400 ml-1">{userId}</span>
        </div>
      ))}
    </div>
  )
}

Building the Comment System

function CommentThread({ comments }: { comments: Comment[] }) {
  return (
    <div className="space-y-4">
      {comments.map(comment => (
        <div key={comment.id} className="rounded-lg bg-gray-800 p-3">
          <div className="flex items-center gap-2 mb-1">
            <div className="h-6 w-6 rounded-full bg-brand-600 flex items-center justify-center text-xs text-white">
              {comment.author[0]}
            </div>
            <span className="text-sm font-medium text-white">{comment.author}</span>
            <span className="text-xs text-gray-500">{formatDistanceToNow(comment.createdAt)} ago</span>
          </div>
          <p className="text-sm text-gray-300">{comment.content}</p>
        </div>
      ))}
    </div>
  )
}

Cross-Selling: Deploy Your Collaborative Editor Now

Our Collaborative Editor template provides a full production implementation with live cursors, comments, version history, markdown support, and document sharing. It is built on Next.js with WebSocket support ready to deploy.

Combine with Kanban Board for project management, or Knowledge Base Portal to create collaborative documentation. All templates use the same tech stack for seamless integration.

Ready to Build?

Get started with our production-ready starter kits and ship your project faster.

Browse Starter Kits