A real-time collaborative whiteboard. Open a room, share the link, draw together —
live cursors, per-user undo, chat, and a 4000×2000 canvas you navigate with a minimap.
Rooms. Create a room, get a link, share it. Anyone with the link joins and immediately receives the full drawing history — the board is replayed from the server's move log, so late joiners see the same canvas as everyone else rather than an empty page.
Live cursors. Every participant's pointer is rendered with their name and assigned colour, streamed
over a dedicated mouse_move channel.
Drawing. Freehand paths, rectangles, circles and images, with an RGBA colour picker for both stroke and fill, adjustable line width, and an eraser.
Per-user undo. This is the part worth reading the source for. The server keeps moves in a
Map<socketId, Move[]> rather than one global stack, so your undo removes your last stroke — not
whatever the room drew most recently. Undo is broadcast and replayed on every client.
Selection. Drag a region to select it, then move or delete what is inside it.
Images. Drop an image onto the board; it is resized client-side before being sent, and can be repositioned afterwards.
A canvas bigger than the screen. The board is 4000×2000. You pan around it, and a minimap in the corner shows where you are and where everyone else is drawing.
Chat. A message panel per room, so the conversation stays next to the drawing.
Backgrounds. Switch the canvas background between plain and grid, light and dark.
A single Node process runs both Next.js and an Express server, with Socket.IO attached:
server/index.ts Express + Socket.IO + Next request handler on one port
rooms: Map<roomId, { usersMoves: Map<socketId, Move[]>, drawed: Move[], users: Map }>
Rooms live in server memory — there is no database. That keeps the whole thing to one process and
makes the realtime logic easy to follow, with the honest trade-off that rooms do not survive a restart.
Swapping the Map for Redis is the natural next step if you want persistence.
Socket events: create_room · check_room · join_room · joined_room · leave_room · draw ·
undo · mouse_move · send_msg · user_disconnected
| Framework | Next.js with a custom Express server, TypeScript |
| Realtime | Socket.IO (typed ClientToServerEvents / ServerToClientEvents) |
| Rendering | HTML5 Canvas, driven by hooks — useDraw, useSocketDraw, useMovesHandlers, useSelection, useBoardPosition |
| State | Recoil |
| UI | Tailwind CSS, Framer Motion, react-colorful, react-toastify |
git clone https://github.com/kuluruvineeth/digiboard.git
cd digiboard
npm install
npm run devOpen http://localhost:3000. No environment variables and no database — create a room, open the link in a second browser window, and you are collaborating with yourself.
For production, the server has to be built alongside Next:
npm run build # build:server + build:next
npm startA /health endpoint is exposed for load-balancer checks. Because state is in memory, run one instance
unless you move rooms into a shared store first.
Built from an empty directory across three sessions, unedited.
▶ 1 — Demo and walkthrough (10m) · 2 — Part 1, the full build (10h 12m) · 3 — Part 2 (4h 39m) · Playlist
Built by @kuluruvineeth — I build AI products and take them apart on camera.