Realtime Collaborative Workspace
Next.js + Yjs CRDT + y-websocket: shared notes that sync live between browsers, with presence count.
8 steps
Shown with defaults: npm, pip, Node LTS, Python 3.12 and the template's default add-ons.
1. Install and select Node.js with nvm
runtimenvm lets you install several Node.js versions side by side and switch per project. `nvm use` activates it for this shell.
bashnvm install --ltsnvm use --lts- Expected result
- `node -v` prints the selected version.
- Verify
- node -v && npm -v
- OS notes
- macOS/Linux/WSL: install nvm with `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash`, then reopen the terminal. Windows: use nvm-windows (github.com/coreybutler/nvm-windows) from an elevated terminal, or fnm.
2. Scaffold a Next.js app
templatecreate-next-app generates an App Router project. Flags make it non-interactive and reflect your add-on choices (TypeScript, Tailwind, ESLint).
bashnpx create-next-app@latest nvx-workspace --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --yes- Expected result
- A ./nvx-workspace folder with src/app, package.json and installed dependencies.
- Verify
- ls nvx-workspace/src/app
- OS notes
- Requires Node.js 20.9 or newer. On Windows, run in PowerShell or Windows Terminal.
3. Enter the project folder
templateThe scaffolder created ./nvx-workspace. Move into it before installing anything else.
bashcd nvx-workspace- Expected result
- You are inside ./nvx-workspace
- Verify
- ls package.json
4. Install Yjs and the WebSocket provider + server
templateyjs is the CRDT, y-websocket is the browser provider, @y/websocket-server is the small sync server (y-websocket v3 no longer ships it). It is pinned to 0.1.1, the last release compatible with Yjs 13 (newer ones require the Yjs 14 pre-release).
bashnpm install yjs y-websocketnpm install -D @y/websocket-server@0.1.1- Expected result
- Packages listed in package.json.
- Verify
- npm ls yjs
5. Add a script for the sync server
templateThe server listens on localhost:1234 by default (override with HOST / PORT env vars).
bashnpm pkg set 'scripts.ws=y-websocket'Files written by this step: .env.local
.env.localNEXT_PUBLIC_YJS_URL=ws://localhost:1234- Expected result
- `ws` script exists in package.json.
- Verify
- npm pkg get scripts.ws
6. Write the collaborative page
templateA textarea bound to a Y.Text; awareness tracks who is connected.
Files written by this step: src/app/page.tsx
src/app/page.tsx"use client"; import { useEffect, useState } from "react"; import * as Y from "yjs"; import { WebsocketProvider } from "y-websocket"; // One shared document per browser tab. Every tab connected to the same room sees the same text. const doc = new Y.Doc(); const shared = doc.getText("notes"); export default function Workspace() { const [text, setText] = useState(""); const [status, setStatus] = useState("connecting"); const [peers, setPeers] = useState(1); useEffect(() => { const url = process.env.NEXT_PUBLIC_YJS_URL || "ws://localhost:1234"; const provider = new WebsocketProvider(url, "nvx-workspace-room", doc); const onText = () => setText(shared.toString()); const onPeers = () => setPeers(provider.awareness.getStates().size); provider.on("status", (event) => setStatus(event.status)); provider.awareness.on("change", onPeers); shared.observe(onText); onText(); return () => { shared.unobserve(onText); provider.awareness.off("change", onPeers); provider.destroy(); }; }, []); return ( <main className="mx-auto max-w-3xl space-y-4 p-8"> <h1 className="text-3xl font-bold">nvx-workspace</h1> <p className="text-sm" aria-live="polite"> Status: <strong>{status}</strong> - people here: <strong>{peers}</strong> </p> <label htmlFor="notes" className="block font-medium">Shared notes (open this page in two windows)</label> <textarea id="notes" className="h-80 w-full rounded-xl border p-4 font-mono" value={text} onChange={(e) => { const value = e.target.value; doc.transact(() => { shared.delete(0, shared.length); shared.insert(0, value); }); }} /> </main> ); }- Expected result
- src/app/page contains the workspace.
7. Terminal 1: start the sync server
templaterun manually · dev serverKeep this running.
bashnpm run ws- Expected result
- Prints: running at 'localhost' on port 1234
8. Terminal 2: start Next.js
templaterun manually · dev serverOpen http://localhost:3000 in two windows and type — text syncs instantly.
bashnpm run dev- Expected result
- Status shows connected and people here: 2.
- Verify
- curl -I http://localhost:3000