Skip to content
CollaborativeNode.js

Realtime Collaborative Workspace

Next.js + Yjs CRDT + y-websocket: shared notes that sync live between browsers, with presence count.

Open in builder →

8 steps

Shell

Shown with defaults: npm, pip, Node LTS, Python 3.12 and the template's default add-ons.

  1. 1. Install and select Node.js with nvm

    runtime

    nvm lets you install several Node.js versions side by side and switch per project. `nvm use` activates it for this shell.

    bash
    nvm 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. 2. Scaffold a Next.js app

    template

    create-next-app generates an App Router project. Flags make it non-interactive and reflect your add-on choices (TypeScript, Tailwind, ESLint).

    bash
    npx 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. 3. Enter the project folder

    template

    The scaffolder created ./nvx-workspace. Move into it before installing anything else.

    bash
    cd nvx-workspace
    Expected result
    You are inside ./nvx-workspace
    Verify
    ls package.json
  4. 4. Install Yjs and the WebSocket provider + server

    template

    yjs 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).

    bash
    npm install yjs y-websocketnpm install -D @y/websocket-server@0.1.1
    Expected result
    Packages listed in package.json.
    Verify
    npm ls yjs
  5. 5. Add a script for the sync server

    template

    The server listens on localhost:1234 by default (override with HOST / PORT env vars).

    bash
    npm pkg set 'scripts.ws=y-websocket'
    Files written by this step: .env.local
    .env.local
    NEXT_PUBLIC_YJS_URL=ws://localhost:1234
    
    Expected result
    `ws` script exists in package.json.
    Verify
    npm pkg get scripts.ws
  6. 6. Write the collaborative page

    template

    A 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. 7. Terminal 1: start the sync server

    templaterun manually · dev server

    Keep this running.

    bash
    npm run ws
    Expected result
    Prints: running at 'localhost' on port 1234
  8. 8. Terminal 2: start Next.js

    templaterun manually · dev server

    Open http://localhost:3000 in two windows and type — text syncs instantly.

    bash
    npm run dev
    Expected result
    Status shows connected and people here: 2.
    Verify
    curl -I http://localhost:3000