Building Offline-First React Native Apps That Don't Break Sync
Offline-first sounds simple. In practice, three failure modes, silent data loss, sync conflicts, broken state on reconnect, kill most attempts. Here's the architecture that survives real usage.
Key takeaways
- Use a local-first database (WatermelonDB, RxDB, SQLite via op-sqlite, or a CRDT like Yjs/Automerge).
- Treat sync as a separate queue, not a per-action HTTP call.
- Design conflict resolution explicitly, last-write-wins is rarely what you want.
- Build observability into sync from day one.
Why this matters
Offline-first apps that "mostly work" lose user data, frustrate field staff, and erode trust. The teams that get it right architect for offline from the start, not as a fallback layer added later.
The core pattern
Local-first data layer
All reads and writes go to a local database first. The app is fully usable without network.
Sync queue
Writes are queued for eventual sync to the server. The queue is durable (survives app restarts), idempotent (repeating a sync attempt is safe), and ordered (parent records before children).
Sync engine
Background process that drains the queue when network is available. Handles retries, backoff, partial failures.
Conflict resolution
When the server already has a different version of a record, decide: last-write-wins (with timestamp), server-wins, client-wins, or merge. Often per-field.
Library choices
WatermelonDB: mature, SQLite-backed, opinionated. Good for relational data.
RxDB: reactive, observable-based, supports multiple storage adapters. Good for live UI updates.
op-sqlite + custom: raw control, more work, fastest.
Yjs / Automerge (CRDT): when you need real conflict-free merging, collaborative editing, multiplayer.
The conflict resolution decision
Make it explicit per entity. For an order: last-write-wins usually fine. For a shared document: CRDT. For inventory counts: server-authoritative.
Common pitfalls
Storing the auth token in async storage and forgetting it can expire mid-sync. Build re-auth into the sync engine.
Treating "we're online" as a binary. It's a spectrum, connected but slow, connected but lossy, captive portal, etc.
Skipping observability. When sync silently fails, you find out from a customer. Log everything.
Not handling app upgrade migrations on local data. Schema migrations on SQLite are needed; plan for them.
What we recommend
WatermelonDB for typical CRUD apps. RxDB if you want reactive UI updates. Custom sync engine, not a one-size-fits-all library. Treat sync as an engineering concern with its own observability dashboard.
FAQs
Does Firebase do this? Firestore has offline support but the abstraction leaks, works for simple apps, struggles at scale.
What about iOS Core Data? If you're React Native, you're better off in cross-platform layers.
How big can the local DB get? Several hundred MB is fine; multi-GB needs care.
