Real-time Yjs synchronization for Convex.
Most Yjs providers rely on WebSockets (like y-websocket). While effective,
they require maintaining a separate server or serverless fleet. y-convex
offers several advantages:
- Serverless: No WebSocket servers to manage. All sync logic runs as Convex functions.
- Transactional: Updates are stored with Convex's ACID guarantees, ensuring document integrity.
- Unified Backend: Keep your collaborative state alongside your other application data in Convex.
- Reactive: Leverages Convex's built-in reactivity to broadcast changes to all clients instantly.
- 🚀 Real-time Sync: Collaborative editing with zero-latency feeling using Convex's reactive engine.
- 💾 Persistent Storage: Changes are automatically persisted to Convex.
- 📸 Automatic Snapshotting: Automatically compresses document history into snapshots to keep database queries fast and storage efficient.
- ⚛️ React-First: Includes a drop-in React hook for easy integration.
- 🔒 Conflict-Free: Leverages Yjs's CRDT (Conflict-free Replicated Data Type) logic for reliable state merging.
pnpm add y-convex yjsCreate a convex/convex.config.ts file (or update your existing one) to include
the y-convex component:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import yconvex from "y-convex/convex.config";
const app = defineApp();
app.use(yconvex);
export default app;Create a file named convex/yconvex.ts to expose the component's functionality
to your application:
// convex/yconvex.ts
import { exposeApi } from "y-convex";
import { components } from "./_generated/api";
export const { init, push, pull } = exposeApi(components.yconvex);Now you can use the useYConvexSync hook to synchronize a Yjs Doc with
Convex:
import { useMemo } from "react";
import * as Y from "yjs";
import { useYConvexSync } from "y-convex/react";
import { api } from "../convex/_generated/api";
export default function CollaborativeEditor({ docId }) {
// 1. Initialize a Y.Doc
const doc = useMemo(() => new Y.Doc(), []);
// 2. Sync it with Convex
// Note: api.yconvex refers to the file created in step 2
useYConvexSync(api.yconvex, docId, doc);
// 3. Use standard Yjs patterns
const yText = doc.getText("content");
// ... rest of your editor logic
}- Initialization: When the hook mounts, it calls
initto fetch the current server state vector and merged updates. - Pushing Changes: Local updates on the
Y.Docare caught via theupdateevent and pushed to Convex using apushmutation. - Pulling Changes: The hook subscribes to a
pullquery that reactively provides new updates from other clients.
y-convex includes a built-in snapshotting mechanism. When the number of
incremental updates for a document exceeds a threshold (default 100), the
component automatically:
- Merges all existing updates and snapshots into a single block.
- Uploads this merged state to Convex File Storage.
- Cleans up the old incremental updates from the database.
This ensures that the initial load time remains fast even for long-lived documents with thousands of edits.
Used in your convex/ directory to re-export the component's functions.
A React hook to synchronize a Yjs doc.
api: The API object containinginit,push, andpull(created viaexposeApi).docId: A string identifying the document.doc: TheY.Docinstance to sync.
The repository includes an example project to help you get started or test changes.
-
Clone and Install:
git clone https://github.com/nucleus48/y-convex.git cd y-convex pnpm install -
Run Development Mode:
pnpm dev
This will start the Convex backend and a Vite frontend for the example app.
ISC