●MAX — Rork Max generates native Swift apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessage●NATIVE — It reaches AR/LiDAR scanning, Metal 3D games, widgets, Live Activities, and on-device Core ML●FUNDING — Rork raised $2.8M from a16z, with 743K monthly visits and 85% growth●PRICING — It's free to start, with paid plans beginning at $25 per month●FLOW — Describe your idea in plain English to get working code, a shareable test link, and iOS/Android builds●COMPARE — The original Rork builds cross-platform apps on Expo/React Native; choose the right tool per goal●MAX — Rork Max generates native Swift apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessage●NATIVE — It reaches AR/LiDAR scanning, Metal 3D games, widgets, Live Activities, and on-device Core ML●FUNDING — Rork raised $2.8M from a16z, with 743K monthly visits and 85% growth●PRICING — It's free to start, with paid plans beginning at $25 per month●FLOW — Describe your idea in plain English to get working code, a shareable test link, and iOS/Android builds●COMPARE — The original Rork builds cross-platform apps on Expo/React Native; choose the right tool per goal
Rork Max × Liveblocks / Yjs: Real-Time Collaborative App Development
A complete guide to integrating Liveblocks and Yjs into Rork Max apps for real-time collaborative editing. From CRDT fundamentals to production deployment, everything you need to build multi-user apps.
Setup and context: Building the "Edit Together" Experience
Features like simultaneous multi-user editing — think Google Docs or Figma — are quickly becoming table stakes for modern productivity apps. Yet real-time collaborative editing has traditionally been one of the hardest areas to implement correctly.
By combining Rork Max with Liveblocks and Yjs, you can bring this complex capability to mobile apps in a surprisingly straightforward way. This guide walks you through everything from the fundamentals of CRDTs to a fully working collaborative editing app, step by step.
This article targets developers who are comfortable with Rork Max basics and have some experience building React Native / Expo apps. Familiarity with Supabase or Firebase will help but isn't required.
What Is a CRDT? The Math Behind Conflict-Free Sync
The foundation of real-time collaborative editing is the CRDT (Conflict-free Replicated Data Type) — a data structure designed so that concurrent edits from multiple users always converge to the same result, regardless of the order updates arrive.
Compared to the older Operational Transformation (OT) approach (used by early Google Docs), CRDTs offer:
Mathematically guaranteed convergence — any ordering of operations produces the same final state on every client
Offline-first by design — users can keep editing when disconnected; changes merge automatically on reconnect
Decentralized architecture — no single point of failure; P2P sync is possible
Yjs is the leading JavaScript CRDT library, providing these shared data types:
Y.Text — collaborative text (insertions and deletions)
Y.Array — collaborative arrays
Y.Map — collaborative key-value maps
Y.XmlFragment — collaborative rich text / HTML
Liveblocks sits on top of Yjs as a hosted real-time infrastructure layer, managing WebSocket servers and scaling for you so you can focus entirely on your app logic.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Understand CRDTs and Yjs to implement conflict-free real-time synchronization across multiple users
✦Integrate Liveblocks into Rork Max apps to build presence indicators, cursor tracking, and live collaboration features
✦Design a scalable collaborative backend by combining Liveblocks with Supabase Realtime
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Liveblocks Presence — who's online, where their cursor is (awareness layer)
Yjs via Liveblocks — CRDT-backed document sync (zero conflicts)
Supabase Realtime — channel notifications and metadata (title, tags, permissions)
Supabase Storage — file and image attachments
Step 1: Liveblocks Account and Project Setup
Create an account at liveblocks.io and create a new project. The free plan covers up to 5,000 MAU and 50 concurrent connections — plenty for early-stage apps.
In Rork Max's terminal:
# Install Yjs and the Liveblocks SDKnpx expo install yjs @liveblocks/client @liveblocks/react# Liveblocks-compatible Yjs providernpx expo install @liveblocks/yjs
-- Document tableCREATE TABLE documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL DEFAULT 'Untitled Document', owner_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, liveblocks_room_id TEXT UNIQUE NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), is_public BOOLEAN DEFAULT FALSE);-- Collaborator permissionsCREATE TABLE document_collaborators ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID REFERENCES documents(id) ON DELETE CASCADE, user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, permission TEXT CHECK (permission IN ('view', 'edit', 'admin')), invited_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(document_id, user_id));-- Row Level SecurityALTER TABLE documents ENABLE ROW LEVEL SECURITY;CREATE POLICY "Owners have full access" ON documents FOR ALL USING (auth.uid() = owner_id);CREATE POLICY "Collaborators can view" ON documents FOR SELECT USING ( EXISTS ( SELECT 1 FROM document_collaborators WHERE document_id = id AND user_id = auth.uid() ) OR is_public = TRUE );
Step 8: Offline Support and Reconnection Handling
One of Yjs's greatest strengths is its offline-first design. Users continue editing without a network connection, and changes merge automatically on reconnect.
// src/hooks/useOfflineSync.tsimport { useEffect, useState } from "react";import NetInfo from "@react-native-community/netinfo";import AsyncStorage from "@react-native-async-storage/async-storage";import * as Y from "yjs";const PREFIX = "yjs_doc_";export function useOfflineSync(ydoc: Y.Doc, documentId: string) { const [isOnline, setIsOnline] = useState(true); useEffect(() => { const unsub = NetInfo.addEventListener((state) => { setIsOnline(state.isConnected ?? false); }); return unsub; }, []); // Persist Yjs state to AsyncStorage (debounced 2s) useEffect(() => { let timer: ReturnType<typeof setTimeout>; const save = () => { clearTimeout(timer); timer = setTimeout(async () => { const state = Y.encodeStateAsUpdate(ydoc); const base64 = Buffer.from(state).toString("base64"); await AsyncStorage.setItem(`${PREFIX}${documentId}`, base64); }, 2000); }; ydoc.on("update", save); return () => { ydoc.off("update", save); clearTimeout(timer); }; }, [ydoc, documentId]); // Load local state on mount useEffect(() => { (async () => { const base64 = await AsyncStorage.getItem(`${PREFIX}${documentId}`); if (base64) { const state = Buffer.from(base64, "base64"); Y.applyUpdate(ydoc, state); // Merges with any remote state } })(); }, [ydoc, documentId]); return { isOnline };}
Performance Tips for Large Documents
When your documents grow large (tens of thousands of characters), keep these optimizations in mind.
1. Yjs state snapshots
// Periodically snapshot the document state to trim the update historyconst snapshot = Y.snapshot(ydoc);const encoded = Y.encodeSnapshot(snapshot);// Save to Supabase Storage; new clients load from the snapshot
2. Rich text with @10play/tentap-editor
For documents exceeding 10,000 characters, native TextInput can become sluggish. Consider @10play/tentap-editor — a Tiptap wrapper for React Native that handles virtualization and Yjs integration out of the box.
3. Batch updates
// Bundle many changes into a single WebSocket messageydoc.transact(() => { yText.insert(0, "batched "); yText.insert(8, "update");});
Summary: Collaboration as a Competitive Advantage
Real-time collaborative editing used to be something only well-funded engineering teams could ship. With Liveblocks and Yjs, indie developers can now add it to Rork Max apps with a reasonable amount of effort.
Here's what we covered:
CRDTs (Yjs) — the math that makes conflict-free sync possible
Liveblocks client setup and presence management
Diff-based text sync — bridging TextInput with a Yjs document
Supabase for durable storage and sharing permissions
Offline-first design — keep editing when the network drops
Your next step: add rich text support with @10play/tentap-editor or collaborative drawing using a shared Y.Map, and you'll have a productivity app that rivals what large teams build.
Advanced Pattern: Collaborative Drawing Board
Beyond text, Yjs's Y.Map lets you synchronize structured data like shapes on a canvas. Here's a minimal collaborative whiteboard implementation:
Every stroke a user draws is immediately replicated to all connected clients via Yjs, with no server-side conflict resolution needed.
Implementing Room-Level Access Control with Liveblocks Auth
For production apps you'll want to verify that only authorized users can join a Liveblocks room. Liveblocks supports server-side authentication via a webhook:
// app/api/liveblocks-auth/route.ts (Next.js / Expo Router API route)import { Liveblocks } from "@liveblocks/node";import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";import { cookies } from "next/headers";const liveblocks = new Liveblocks({ secret: process.env.LIVEBLOCKS_SECRET_KEY!,});export async function POST(request: Request) { // 1. Verify the user's Supabase session const supabase = createRouteHandlerClient({ cookies }); const { data: { session } } = await supabase.auth.getSession(); if (!session) { return new Response("Unauthorized", { status: 401 }); } // 2. Verify the user has access to the requested room const { room } = await request.json(); const documentId = room.replace("document-", ""); const { data: access } = await supabase .from("document_collaborators") .select("permission") .eq("document_id", documentId) .eq("user_id", session.user.id) .single(); const { data: doc } = await supabase .from("documents") .select("owner_id, is_public") .eq("id", documentId) .single(); const isOwner = doc?.owner_id === session.user.id; const isCollaborator = !!access; const isPublic = doc?.is_public; if (!isOwner && !isCollaborator && !isPublic) { return new Response("Forbidden", { status: 403 }); } // 3. Issue a Liveblocks auth token const { body, status } = await liveblocks.identifyUser( { userId: session.user.id, groupIds: [], }, { userInfo: { name: session.user.user_metadata?.full_name ?? "Anonymous", avatar: session.user.user_metadata?.avatar_url, }, } ); return new Response(body, { status });}
With this in place, the Liveblocks client sends a token request to your API before joining any room, and only verified users can connect.
Undo / Redo: History Management with Yjs UndoManager
Adding undo/redo to a collaborative editor is surprisingly tricky — you only want to undo your own changes, not your collaborators'. Yjs ships a built-in UndoManager for exactly this:
// src/hooks/useUndoManager.tsimport { useEffect, useRef } from "react";import * as Y from "yjs";export function useUndoManager(yText: Y.Text) { const managerRef = useRef<Y.UndoManager | null>(null); useEffect(() => { // Track only changes to yText; ignore remote changes automatically const manager = new Y.UndoManager(yText, { // Merge operations within 500ms into a single undo step captureTimeout: 500, }); managerRef.current = manager; return () => manager.destroy(); }, [yText]); return { undo: () => managerRef.current?.undo(), redo: () => managerRef.current?.redo(), canUndo: () => (managerRef.current?.undoStack.length ?? 0) > 0, canRedo: () => (managerRef.current?.redoStack.length ?? 0) > 0, };}
Wire this into your editor's toolbar buttons, and each user gets independent undo history that never steps on their collaborators' work.
Testing Collaborative Features Locally
Testing multi-user scenarios requires multiple simultaneous sessions. The quickest approach during development:
Option A — Multiple simulators
Run two iOS simulators (or one iOS + one Android) connected to the same Liveblocks room. Edit on one device and watch changes appear on the other in real time.
Option B — Split browser + device
Open a web preview of your Rork Max app (via Expo's web target) in two browser tabs. Both tabs share the same WebSocket connection pool through Liveblocks, making it easy to see presence and sync behavior.
Option C — Automated integration tests
// tests/collaboration.test.tsimport * as Y from "yjs";import { WebsocketProvider } from "y-websocket"; // local y-websocket servertest("two clients converge on the same text", async () => { const doc1 = new Y.Doc(); const doc2 = new Y.Doc(); // Simulate a peer-to-peer update exchange const text1 = doc1.getText("content"); const text2 = doc2.getText("content"); // Client 1 types "Hello" text1.insert(0, "Hello"); Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1)); // Client 2 types " World" concurrently text2.insert(5, " World"); Y.applyUpdate(doc1, Y.encodeStateAsUpdate(doc2)); // Both documents should converge expect(text1.toString()).toBe("Hello World"); expect(text2.toString()).toBe("Hello World");});
This kind of deterministic unit test validates CRDT convergence without needing a live WebSocket server.
Deployment Checklist
Before shipping your collaborative app to the App Store or Google Play, run through these checks:
Liveblocks configuration
Replace the development public key with your production pk_live_... key
Set up server-side authentication (the /api/liveblocks-auth endpoint above)
Configure rate limits in the Liveblocks dashboard to prevent abuse
Supabase RLS
Verify that Row Level Security policies are enabled on all tables
Test with a non-owner account to confirm they can't access documents they haven't been invited to
Yjs document size
Monitor document sizes in Liveblocks analytics; documents over ~1 MB benefit from snapshot-based loading
Set up a webhook to archive inactive documents to Supabase Storage after 30 days
Offline graceful degradation
Test the app in airplane mode: editing should continue, and changes should merge on reconnect
Display a clear "offline" indicator so users know sync is paused
Error boundaries
// Wrap the RoomProvider in an error boundary for graceful fallbackimport { ErrorBoundary } from "react-error-boundary";function CollaborationFallback() { return <OfflineEditor />; // Read-only fallback when Liveblocks is unavailable}export function CollaborationWrapper({ children }: { children: React.ReactNode }) { return ( <ErrorBoundary FallbackComponent={CollaborationFallback}> {children} </ErrorBoundary> );}
Monetizing Collaborative Apps: Subscription Tiers That Make Sense
Real-time collaboration is a natural premium feature. Here's a tiered structure that works well for productivity apps built on Rork Max:
Gate the collaboration features using your existing Stripe integration (see Rork × Stripe サブスクリプション実装ガイド for the full implementation). The check is straightforward:
This keeps your monetization logic clean and easy to adjust as you learn what your users value most.
Real-World Use Cases for Collaborative Rork Apps
To spark ideas for your own product, here are categories where real-time collaboration adds clear, measurable value:
Note-taking and knowledge management
Think Notion-style team wikis or meeting note apps. Multiple attendees can contribute to the same document in real time during a call.
Design feedback and annotation
Display a shared image (loaded from Supabase Storage) and let reviewers place comments or markup using a collaborative Y.Map of annotation objects.
Code review and pair programming
A lightweight code editor where developers can discuss changes inline. Combine Yjs text sync with Liveblocks comments for a mobile-native code review experience.
Customer support and live chat triage
Support agents share a collaborative queue of tickets. Agent presence indicators show who is handling which ticket, preventing duplicate responses.
Education and tutoring
Students and teachers co-edit worksheets or solve problems together. The teacher's cursor and annotations are visible to all students in real time.
Each of these verticals has strong willingness to pay for the "live collaboration" tier, which makes the Liveblocks subscription cost easy to justify.
A Note from an Indie Developer
What's Next?
You now have a solid foundation for building real-time collaborative apps with Rork Max, Liveblocks, and Yjs. The path forward:
Add rich text formatting with @10play/tentap-editor (bold, italic, headings, lists — all CRDT-safe)
Integrate Liveblocks Comments for threaded discussions anchored to specific text ranges
Explore Liveblocks Notifications to send push alerts when someone mentions a user (@username) in a document
Scale to Y.js subdocuments for large multi-page documents that lazy-load individual pages on demand
The real-time collaborative space is still wide open on mobile. Apps that nail the "edit together, anywhere" experience — with reliable offline support and a clean subscription model — have a genuine shot at standing out in crowded App Store categories.
We hope this guide gives you a clear, practical path from zero to a working collaborative app. If you build something with this stack, we'd love to hear about it — feel free to share your project on the Rork Lab blog or reach out via the community channels. The best apps get built when developers share what they've learned along the way.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.