Beyond the Sync: Engineering Real-Time Collaborative Workspaces with CRDTs and Expo
Stop fighting race conditions and 'Last Write Wins' logic in your mobile apps. This post explores how I integrated CRDTs with Expo to build high-performance, offline-ready collaborative environments.

Beyond the Sync: Engineering Real-Time Collaborative Workspaces with CRDTs and Expo
For years, we’ve been lying to ourselves about "real-time." We built apps that relied on basic WebSockets and a prayer, hoping that the "Last Write Wins" (LWW) strategy wouldn't destroy a user's data when two people edited a field simultaneously on a spotty 5G connection.
I’ve spent the last few months rebuilding a complex collaborative workspace in an Expo-based mobile app. The breakthrough wasn't a faster socket server—it was moving away from state synchronization and toward state convergence using CRDTs (Conflict-free Replicated Data Types).
Here is how I approached the architecture and the technical hurdles I cleared along the way.
The Fundamental Shift: From Syncing to Convergence
In a standard REST or GraphQL setup, your UI is a reflection of a central database. In a collaborative environment, this creates a massive lag between intent and visibility. If I’m on a train with high latency, my "optimistic update" will eventually conflict with yours, and the server will have to pick a winner.
CRDTs change the game. They allow multiple replicas (devices) to be updated independently and concurrently without coordination. The data structure itself guarantees that once all replicas have seen the same set of updates, they will reach the exact same state. No central arbiter required.
The Stack: Expo + Yjs
For this project, I chose Yjs because of its modularity and high performance. While many developers reach for Automerge, I found the binary encoding in Yjs to be significantly lighter for mobile devices where memory overhead and battery life are premium constraints.
Setting up the Shared Document
In an Expo app, you want your CRDT state to live outside the standard React render cycle to prevent performance bottlenecks. I wrapped the Yjs logic in a custom hook that manages the Y.Doc lifecycle.
The Mobile Challenge: Performance and React Integration
The biggest pitfall I encountered was re-rendering the entire mobile screen every time a remote user typed a single character. In a document with 10 people, your FlatList will melt.
I solved this by using Selective Subscriptions. Instead of pushing the entire Y.Doc into React state, I used a "Proxy" approach. Libraries like @syncedstore/core are great, but for raw performance in Expo, I preferred using valtio or a simple event-emitter pattern to update only the specific component that needed the change.
Tip: Avoiding the "Double-Update" Loop
When you update a shared state, you don't want to re-trigger the change logic from your own input. Yjs handles this via transaction.origin.
By tagging your transactions, your observers can ignore updates that originated from the local user, preventing unnecessary UI jitter.
Offline-First is No Longer a Feature—It's a Requirement
The beauty of CRDTs in an Expo app is that offline support comes almost for free. When the HocuspocusProvider (or any Yjs provider) loses connection, the local Y.Doc continues to function.
I integrated IndexedDB (via y-indexeddb) on web and SQLite on native Expo to persist the binary update logs locally. When the app re-opens, it loads the binary blob, applies it to the Doc, and the provider automatically syncs only the diffs with the server once the connection is restored.
Lessons from the Trenches
- Binary over JSON: Always transfer state as
Uint8Array. It’s significantly faster to parse on mobile CPUs than massive JSON trees. - Cursor Presence: Don't store ephemeral data like cursor positions in the CRDT document itself. Use the Awareness API. It handles the "user left the app" cleanup automatically without bloating the document history.
- The Memory Ceiling: Large CRDT documents can grow. Periodically, you should "compact" the document on your server to flatten the update history, otherwise, the initial sync for a new mobile client might take seconds.
Final Thoughts
Engineering a collaborative workspace in Expo isn't just about real-time sockets anymore. It's about distributed systems. By offloading the conflict resolution logic to CRDTs, we can focus on building snappy, intuitive interfaces that feel local even when the user is miles away from a stable tower.
If you're still building with "Last Write Wins," it's time to re-evaluate. The tools are here, and the performance overhead is negligible compared to the massive boost in user experience.