FigPro: Multiplayer Canvas for the Web
FigPro is a Figma-inspired collaborative whiteboard that brings the magic of real-time design collaboration to any web browser. Built with Next.js 14, Liveblocks, and Fabric.js, it proves that building multiplayer experiences doesn't have to be overwhelming.
The Vision
Designers and product teams love collaborative canvases like FigJam, but implementing cursor chat, emoji reactions, and shared drawing tools from scratch feels daunting. FigPro demystifies that stack—showing how little code you need to ship a real-time playground that feels fun, fast, and familiar.
What It Does
🎨 Full-Featured Canvas
- Draw rectangles, circles, triangles, lines, and freehand paths
- Add text annotations and upload images
- Pan, zoom, and select multiple objects
- Undo/redo with full history support
👥 Multiplayer Magic
- Live cursors showing where teammates are working
- Cursor chat for quick communication
- Emoji reactions that burst across the canvas
- Active user list that updates as people join/leave
💬 Collaborative Comments
- Anchor comment threads to specific canvas positions
- React to comments with emoji
- Mark threads as resolved
- Persistent storage via Liveblocks
Because we use Liveblocks for presence, history, and comments, we get optimistic updates and conflict resolution "for free" without building a WebSocket server.
Technical Architecture
The Stack
- Next.js 14 App Router – Hosts the experience with SSR disabled for the canvas (Fabric.js needs DOM access)
- Liveblocks – Powers real-time presence, reactions, and shared storage with
LiveMap - Fabric.js – Handles canvas rendering, object manipulation, and drawing tools
- React Context – Manages canvas state and keyboard shortcuts
How It Works
Liveblocks Room Context wires together presence, reactions, and shared storage. We maintain a LiveMap of canvasObjects that mirrors Fabric objects across all connected clients. The generated hooks (useStorage, useMutation, useUndo) make state synchronization declarative.
Fabric.js Renderer translates mouse gestures into canvas objects and immediately syncs them via Liveblocks mutations. Event handlers manage drawing, selecting, scaling, and zooming while keeping the shared state in lockstep.
Presence-Driven UI broadcasts transient events (cursor movements, chat messages, emoji reactions) over Liveblocks while debouncing reaction trails and keyboard shortcuts for smooth performance.
Code Highlights
Syncing Fabric State
Every Fabric object is serialized and stored in the shared LiveMap:
const syncShapeInStorage = useMutation(({ storage }, object) => {
if (!object) return
const { objectId } = object
const shapeData = object.toJSON()
shapeData.objectId = objectId
const canvasObjects = storage.get('canvasObjects')
canvasObjects.set(objectId, shapeData)
}, [])
When any client mutates the map, the useStorage subscription triggers a re-render, keeping everyone synchronized.
Multiplayer Cursors & Reactions
const [{ cursor }, updateMyPresence] = useMyPresence()
const broadcast = useBroadcastEvent()
useInterval(() => {
if (
cursorState.mode === CursorMode.Reaction &&
cursorState.isPressed &&
cursor
) {
setReactions((reactions) =>
reactions.concat([
{ point: cursor, value: cursorState.reaction, timestamp: Date.now() },
])
)
broadcast({ x: cursor.x, y: cursor.y, value: cursorState.reaction })
}
}, 100)
The canvas listens for keyboard shortcuts like / (chat) and e (reactions), then uses Liveblocks presence and event streams to animate cursors and emoji trails across all participants.
Demo
Key Learnings
What Makes This Special:
✅ Approachable multiplayer – Liveblocks abstracts away WebSocket complexity
✅ Declarative sync – React hooks make real-time state feel like local state
✅ Canvas meets React – Balances imperative drawing with declarative UI
✅ Production-ready patterns – Conflict resolution, history, and presence built-in
Takeaways for Developers:
- Liveblocks is a game-changer for multiplayer apps—no backend infrastructure needed
- Fabric.js + React works beautifully when you respect the imperative/declarative boundary
- Presence APIs make features like live cursors and reactions trivial to implement
- Shared storage with
LiveMapkeeps complex state synchronized effortlessly
This project shows that building collaborative tools is more accessible than ever. Fork the repo on GitHub and remix it for your own product experiments!