What 'Offline-First' Actually Means
"We need offline support" means at least three different things, and conflating them is where a lot of these projects go wrong before a line of code is written. Offline-first, as this guide uses the term, means a user can both read and writedata with no network connection at all — the app's normal write path is "save locally, sync when possible," not "call the server, and cache the response in case the next read has no network." That's a meaningfully bigger architectural commitment than read-only caching, and most teams asking for "offline support" actually mean the smaller thing.
| What Teams Usually Ask For | What It Actually Requires | Where It Breaks If Undersold |
|---|---|---|
| "The app should work without signal" | Read-only caching of already-fetched data — no local writes, no conflict resolution needed | Fine for a content app; a user who tries to submit a form offline just gets an error, which is often an acceptable, honest limitation |
| "Users should be able to keep working offline" | Queued writes with server authority — local writes are recorded and sent once online, but the server is always the final source of truth | Breaks if two devices queue conflicting writes for the same record; needs an explicit conflict policy, covered later in this guide |
| "Multiple people should be able to edit the same thing offline" | True local-first: every device is a first-class writer, and concurrent edits must merge, not just queue — the CRDT territory this guide spends the most time on | Breaks hardest and most silently if under-scoped: a naive implementation quietly drops one user's work whenever two edits collide |
If you're still deciding on a cross-platform framework before any of this becomes relevant, our cross-platform development guide covers the React Native/Flutter/native tradeoff more broadly — every sync engine in this guide has React Native and iOS/Android-native bindings, but which framework you've already committed to narrows the realistic shortlist.
The 2026 Sync Engine Landscape
This isn't a ranked top list — the right engine depends entirely on which backend you already run and how much concurrent-write complexity your data model actually has, which is a fit question, not a benchmark question. What it is: a status check on the players that matter in 2026, since corporate status in this category has moved more in the last eighteen months than in the prior five years combined.
| Sync Engine / Platform | Backend → Local Store | Sync Model | Conflict Resolution | Corporate Status (checked Sep 14, 2026) |
|---|---|---|---|---|
| PowerSync | Postgres, MongoDB, MySQL, SQL Server, or Azure DocumentDB → embedded SQLite | Incremental "sync streams" over a managed sync service, bucketed by query | Last-write-wins by default; app-level custom resolution on conflict | Private company; server-side PowerSync Service moved to the Functional Source License in its May 2024 "Open Edition" launch, client SDKs stayed Apache 2.0/MIT — genuinely self-hostable, not just source-available marketing |
| Electric (ElectricSQL) | Postgres → local Postgres via PGlite, or an app cache, via shape subscriptions | Read-path only — streams Postgres rows out; no built-in write-path back to Postgres | Not applicable to the sync layer itself; your app's own write path owns it | Announced it's joining Databricks Aug 11, 2026 (folding into the Neon/Lakebase team). Open-source components — Postgres Sync, PGlite, TanStack DB, Durable Streams — stay open source; the hosted Electric Cloud service is being wound down |
| RxDB | Any REST/GraphQL/CouchDB/Supabase backend you configure → IndexedDB, SQLite, or OPFS | Custom replication protocol per backend, checkpoint-based pull/push | You supply a conflict handler function; no built-in CRDT merge | Independent, open-core; core is Apache 2.0 but capped at 13 collections, Pro/Pro Plus tiers run roughly €1,300–€2,000/year; shipped v17 in March 2026 |
| WatermelonDB | Any backend you implement a sync protocol against → SQLite via native bridge/JSI | Pull-then-push sync protocol you implement server-side; the library gives you only the client half | Convention-based (typically last-write-wins); no built-in CRDT | Maintained by Nozbe; MIT-licensed and open source, but its most recent npm release was roughly a year old as of when we checked — verify current commit activity before a long-term bet |
| Realm / Atlas Device Sync | Formerly: MongoDB Atlas → embedded Realm database | Formerly: a proprietary managed sync service | Formerly: automatic Realm-native merge | Discontinued. Deprecation announced September 2024, service shut down completely September 30, 2025; the Realm SDK survives only as a local, non-syncing database from v20 onward — see the next section |
| Turso (libSQL) | Turso Cloud (SQLite) → embedded-replica local SQLite file | Log-shipping embedded-replica sync, with offline writes in public beta as of 2026 | App-resolved; no automatic multi-writer CRDT merge | Private company (Turso); actively shipping — its own 2026 guidance now recommends Turso proper over raw libSQL embedded replicas for sync workloads |
| Zero (Rocicorp) | App-defined Postgres backend → client-side query cache | Query-driven sync framework, the successor to Rocicorp's earlier Replicache | Application-defined, per Zero's own conflict model | Rocicorp; Replicache (its five-year-old predecessor) is now in maintenance mode — open source, still supported, no new features — with new projects steered toward Zero |
| Ditto | Peer-to-peer mesh or cloud → embedded CRDT-based store | Automatic CRDT merge; can sync device-to-device with zero server in the loop, not just device-to-cloud | Built-in CRDT conflict resolution — its core differentiator from the rest of this table | Private, well-funded; closed an $82M Series B led by Top Tier Capital Partners and Acrew Capital in March 2025 at roughly a $462M valuation |
| Firestore (built-in offline persistence) | Firestore → on-device cache (IndexedDB on web, native cache on mobile) | Firestore's own real-time listener sync; not a separate product | Last-write-wins per document field; no CRDT | A standard Firestore SDK feature (persistentLocalCache with persistentMultipleTabManager for multi-tab), not a general-purpose sync engine you can point at another backend |
One structural point worth flagging before the next two sections, both of which are about corporate risk specifically: two of the nine rows above changed status within the last thirteen months (Realm's sync shut down entirely; Electric got acquired), and a third (Replicache) was formally superseded by its own maker. A sync engine is core, hard-to-swap infrastructure — treat a vendor selection here with the same scrutiny you'd apply to a database choice, not a UI library choice.
What Actually Happened to Realm and MongoDB Device Sync
Realm was, for years, one of the most widely adopted mobile embedded databases, and its automatic cross-device sync was the single biggest reason teams chose it over a plain SQLite wrapper. MongoDB acquired Realm in 2019 and rebranded the sync service Atlas Device Sync, paired with Atlas Device SDKs. In September 2024, MongoDB announced Device Sync's deprecation; the service shut down completely on September 30, 2025.
What survives: the Realm SDK itself, as a purely local, non-syncing embedded database, remains available and open source from v20 onward — a real, usable piece of software, just missing the half that made Realm distinctive. Community discussion of the shutdown, including a widely-read GitHub discussion titled "The Future: Realm is Deprecated/Dead", reflects how many teams treated managed sync as a permanent platform capability rather than a product line MongoDB could discontinue on its own schedule.
This isn't the first time this exact lesson has shown up on this site. Our deep linking implementation guide makes the identical point about Firebase Dynamic Links, which shut down completely in August 2025 — a free or bundled infrastructure feature from a major platform vendor is not exempt from being discontinued on that vendor's own timeline, and sync engines specifically have now demonstrated this twice in the same eighteen-month window.
Electric's Acquisition by Databricks, and the Vendor-Risk Lesson It Repeats
Electric (the company behind ElectricSQL) announced on August 11, 2026that it is joining Databricks, folding into the team building Neon and Lakebase — Databricks' Postgres-centered platform, following its roughly $1 billion acquisition of Neon in May 2025. Unlike Realm, this isn't a full shutdown: Electric has stated that everything it previously open-sourced — Postgres Sync, PGlite, TanStack DB, and Durable Streams — stays open source.
What a "stays open source" promise does and doesn't protect you from
Open source protects your code from disappearing — you can keep running and even forking the software the day after an acquisition. It does not protect your roadmap, your support SLA, or a hosted service you were paying for. Electric's own hosted Electric Cloud offering is being wound down as part of this transition: existing cloud customers need to self-host the open-source components or migrate to another engine. "The code stays open" and "the product you pay for keeps running" are two different promises — this transition kept the first and broke the second.
One technical caveat worth stating separately from the acquisition, since it's easy to conflate the two: Electric has always been a read-path sync engine — it streams Postgres data out to clients via shape subscriptions, but provides no built-in mechanism for getting local writes back into Postgres. Teams typically pair it with TanStack DB's mutation layer or a hand-rolled write API for the other half. That limitation predates the Databricks announcement and isn't a consequence of it; it's a standing architectural fact to weigh regardless of who owns the company.
Conflict Resolution: CRDTs, Last-Write-Wins, and What Actually Merges
Every offline-first architecture eventually answers the same question: when two devices each changed the same data while disconnected, what happens when they reconnect? Last-write-wins (LWW) keeps whichever edit has the later timestamp and silently discards the other — simple, and often good enough, but it means a user's work can vanish with no notice. A CRDT (Conflict-free Replicated Data Type) is a data structure engineered so concurrent edits merge deterministically without a central arbiter, preserving both changes instead of picking a winner.
| Approach | Best Fit | Adoption / Maturity (as reported, checked Sep 14, 2026) | What It Guarantees | Typical Use Case |
|---|---|---|---|---|
| Last-write-wins (LWW) | Any store; the default absent an explicit policy | Trivial — keep the later timestamp, discard the other write | None — the losing edit vanishes with no record | Single-writer-per-record data; rare true conflicts |
| Yjs | Collaborative text/rich structures; the widest editor-ecosystem support | Production default as of 2026 — reported around 920K weekly downloads and 17K GitHub stars | Automatic CRDT merge; both concurrent edits preserved and combined | Real-time collaborative editors, whiteboards, structured documents |
| Automerge | JSON-like data structures with a plain-JavaScript-object API | Smaller adoption (reported around 85K downloads) but a Git-like change history | Automatic CRDT merge; v3 (July 2025) cut memory use roughly 10x via columnar compression | Apps that want document-level version history alongside merge, not just current state |
| Peritext | Rich-text formatting spans specifically (bold, italic, links) layered over a text CRDT | A published algorithm (Ink & Switch / CSCW 2022), not a single off-the-shelf package | Merges formatting spans deterministically so concurrent formatting edits converge, not just plain text | Rich-text editors where two people format the same passage differently while offline |
Yjs and Automerge solve overlapping but distinct problems: Yjs has the deeper editor-ecosystem integration and is the more common production default, while Automerge's appeal is treating your data like plain JSON with a Git-like change history baked in. Automerge's 3.0 release in July 2025 was a genuine step change, not an incremental one — re-architecting the runtime around columnar compression cut memory consumption roughly tenfold for text-heavy workloads compared to 2.0. Neither library, on its own, correctly handles formatted rich text (bold, italics, links) merging when two people format the same passage differently offline — that specific problem is what Peritext, a published Ink & Switch/CSCW 2022 algorithm, was built to solve, by storing formatting as spans tied to stable character identifiers rather than raw positions.
Reference Architecture: The Outbox and the Order to Build It In
The order matters because each step depends on the one before it, or fails invisibly without it — the same pattern that governs push notification and deep-linking infrastructure on this site applies here, with offline-sync's own specific failure modes swapped in.
| Step | What | Failure Mode If Skipped | Why This Order |
|---|---|---|---|
| 1 | Local-first data model and storage engine selection — embedded SQLite/IndexedDB/OPFS, plus an explicit CRDT-vs-LWW decision per record type. | Retrofitting a sync layer onto a schema never designed for merge is the single most common rebuild trigger in this category | Nothing downstream can be designed correctly without knowing how conflicts on this data will actually be resolved |
| 2 | Outbox / write queue — every local write recorded to a durable local queue synchronously, before any network call is attempted. | A write made offline is silently lost if the app is killed before it ever reaches the network | Depends on step 1's storage engine already existing to hold the queue |
| 3 | Idempotency keys (a client-generated request ID) on every outbound mutation. | A retried request after a timeout double-applies — a duplicated row, a double-logged job ticket, a duplicate charge | Must exist before step 4's retry/dispatcher logic is allowed to run unattended |
| 4 | Sync dispatcher and retry logic — drains the queue on app start, on connectivity restore, on app resume, and once eagerly right after enqueue. | A queue that only drains on a manual pull-to-refresh strands writes for hours or days | Depends on steps 2–3 already being correct, or automated retries themselves become the bug |
| 5 | Conflict resolution layer — CRDT merge, or an explicit last-write-wins/manual-merge policy, applied to what the dispatcher delivers. | Two technicians editing the same field offline get one silently overwritten, with no one ever told | Comes after dispatch, since resolving on receipt is what a working dispatcher actually feeds |
| 6 | Background scheduling integration — BGTaskScheduler (iOS) / WorkManager (Android) — to run the dispatcher without the user having the app open. | Sync only happens while the user is staring at a loading spinner, defeating the point of "offline-first" | Last, because it's an optimization layered on a dispatcher that must already work correctly in the foreground |
The outbox — step 2 — is the load-bearing piece most teams underbuild. It's a durable local queue that records the intent of a write synchronously, before the network call is even attempted, so the intended change survives an app kill or a power loss mid-write. A dispatcher then drains it on app start, on connectivity restore, on app resume, and once eagerly right after the write is enqueued — removing each entry on success, leaving it in place on a transient failure like no network, a server 5xx, or a timeout.
Background Sync: What iOS and Android Actually Guarantee
A dispatcher that only runs while a user is actively looking at the app defeats much of the point of offline-first — but both mobile platforms are far stingier about background execution than most teams building this for the first time expect.
On iOS, the BackgroundTasks frameworksplits background sync into BGAppRefreshTask (short refresh work) and BGProcessingTask (longer jobs the system runs opportunistically, typically when the device is idle and charging). Apple's own documentation is explicit that it doesn't guarantee your requested interval — the system decides when to actually run your task based on the user's usage patterns, current battery state, and expected battery impact. Implementation guides commonly report a practical execution budget around 30 seconds for a refresh task before the OS terminates it, and widely documented developer experience holds that a user force-quitting the app stops iOS from scheduling further background tasks for it at all — treated as an explicit "don't run this unattended" signal from the user.
On Android, WorkManageris the Jetpack-recommended API for work that must survive process death, app restarts, and device reboots — the standard choice for draining an offline write queue. Google's guidance splits background work into three categories: immediate/expedited, long-running (potentially over ten minutes), and deferrable/periodic — and deferrable work respects Doze mode and battery optimization by design, meaning the OS can and will delay it to preserve battery rather than run it on your requested schedule.
Offline UX: Optimistic Writes and Not Lying About 'Synced'
The point of local-first's "fast" ideal is that a write feels instant — the UI updates optimistically the moment a change is queued to the outbox, without waiting on a network round trip. Done well, that's the whole appeal of offline-first over a thin client. Done carelessly, it becomes a way to quietly mislead users about whether their data is actually safe.
- A spinner that never resolves: A generic loading indicator with no distinct state for "queued locally, not yet synced" leaves a user unable to tell whether it's safe to close the app or switch devices after days offline.
- A premature "saved" confirmation: Firing a success toast the instant a write hits the local outbox, with no later correction if the sync ultimately fails (a rejected write, a validation error caught server-side), teaches users to distrust the app's own status messages the first time it happens.
- Unbounded local queue growth: Letting the outbox grow indefinitely with no visible cap, warning, or storage policy turns a temporary connectivity gap into a silent, unbounded liability the user never sees coming.
The fix pattern is an explicit sync-status state machine surfaced to the user — synced, pending, conflict, failed — rather than a binary spinner. And when a CRDT or last-write-wins policy actually resolves a conflict, that resolution deserves a visible trace: "your change to this field was combined with a later edit from [teammate]" treats the user as someone whose work matters, where silent overwrite treats a merge as an implementation detail nobody needed to know about.
Local-First Security: Encryption at Rest and Server-Side Authorization
An offline-first app keeps a meaningful, often complete, copy of user data sitting on the device by design — which raises the stakes of local storage security compared to an app that's mostly a thin client over a server with nothing persisted locally.
The standard defense is encryption at rest for the local database — SQLCipher-style encryption for SQLite, or relying on platform full-disk encryption plus per-file protection classes — combined with storing the actual encryption key in the OS-level secure key store rather than embedding it in app code. Our biometric authentication guide covers how the Secure Enclave-backed Keychain (iOS) and Keystore (Android) actually work in more depth — the mechanism is identical whether you're protecting a biometric-gated secret or a local database encryption key.
There's a genuine, unresolved tension worth naming honestly rather than glossing over: Kleppmann's "privacy" ideal calls for end-to-end encryption, but a server-side CRDT merge process typically needs to read the data it's merging. Fully end-to-end-encrypted multi-writer conflict resolution is an active area of research, not a solved, off-the-shelf capability most 2026 sync engines actually deliver — treat any vendor claiming otherwise with the same skepticism this site applies to an unverified security claim anywhere else.
Build vs. Buy: Hand-Rolled SQLite vs. a Sync Engine Vendor
Hand-rolling — a client-side database like WatermelonDB or RxDB paired with your own outbox, idempotency, and last-write-wins logic — is defensible when your data is effectively single-writer-per-record: a technician closing their own job ticket, a user editing their own profile, with true simultaneous conflicts rare enough that a simple resolution policy and a visible "last updated by" trail cover it adequately.
A vendor earns its fee once your app has genuine concurrent multi-writer editing you don't want to build correctness guarantees for yourself, cross-platform consistency across web, iOS, and Android on one sync layer, or — Ditto's specific niche — sync between devices with no server reachable at all, not just a phone with weak signal. None of the vendors in this guide's landscape section remove your own obligation to design the outbox, idempotency, and conflict-surfacing UX layers described above; they replace the sync transport and merge algorithm, not the whole architecture around it.
If you're scoping this alongside a broader cost conversation, our app total cost of ownership guide covers how a sync engine's licensing model (open-source self-hosted vs. a managed vendor's recurring fee) plays into a five-year cost picture beyond the initial build.
What an Offline Sync Gap Actually Costs: A Worked Scenario
The following is an illustrative worked scenario, not a real client engagement or a reported outcome — the arithmetic uses stated, realistic assumptions to make the cost of a missing idempotency layer concrete.
Consider a 60-technician HVAC and plumbing service company running a field app that lets technicians log completed job tickets, parts used, and customer signatures without a live connection — since a meaningful share of calls happen in basements, rural service areas, or buildings with poor in-building coverage. Assume the company's own dispatch data shows it completes roughly 600 job tickets a day fleet-wide, and that roughly 12% of those closeouts happen somewhere with degraded or no connectivity.
| Scenario | Assumption (stated, not a benchmark) | Monthly / Daily Volume | Downstream Effect |
|---|---|---|---|
| Baseline (fleet-wide job tickets/day) | 600 tickets/day, stated as this company's own measured average | 600 tickets | — |
| Tickets closed out with degraded/no connectivity | Assume, per this company's own field data, roughly 12% of closeouts happen somewhere with poor or no signal | ≈ 72 tickets/day queued to the local outbox | 72 tickets/day now depend on the outbox and dispatcher working correctly |
| Duplicate submissions from a missing idempotency layer | Assume a retry-after-timeout double-submits roughly 2% of queued tickets, a stated illustrative assumption, not a benchmark | ≈ 1–2 duplicate ticket submissions/day | ≈ 30–45 duplicate parts-invoice line items/month for bookkeeping to catch and unwind |
The honest reading of that table isn't that a missing idempotency layer is a catastrophe — most duplicate tickets get caught eventually. The point is that this class of bug is silent by design: nothing crashes, no error surfaces to the technician, and the only visible signal is a bookkeeping team quietly reconciling a small, recurring stream of duplicate parts-invoice line items that looks, from a dashboard, indistinguishable from normal data-entry noise until someone specifically audits for it.
Red Flags in Vendor and Agency Selection
| Claim | Reality |
|---|---|
| "We'll just turn on Firestore's offline persistence, that's offline-first" | Firestore's cache handles read continuity and simple queued writes well, but it's Firestore-specific and offers no CRDT merge or cross-backend flexibility — calling it "offline-first" for a genuinely concurrent multi-writer use case understates what's actually needed. |
| A proposal built around Realm / MongoDB Atlas Device Sync | The service has been fully shut down since September 30, 2025. A 2026 proposal built on it as a going-forward sync layer hasn't kept current with a shutdown that's over a year old. |
| A quoted sync "reliability" or "accuracy" percentage with no stated methodology | We found no independently audited cross-vendor benchmark for offline-sync reliability, for the same reason we refuse unsourced percentages elsewhere on this site — a single headline number describes that vendor's aggregate customer base, not your app's actual conditions. |
| No mention of idempotency keys or duplicate-write handling | The outbox pattern gives at-least-once delivery by design, not exactly-once. A proposal silent on duplicate handling hasn't actually shipped an offline write queue into production before. |
| No answer for what happens after a device sits offline for weeks | Queue growth, local storage caps, and stale-conflict resolution windows all need an explicit, stated policy — "it'll sync eventually" isn't one. |
If you're evaluating competing proposals for a broader app rebuild that happens to include offline sync as one piece, our mobile app RFP template guide covers how to write scope language specific enough that different agencies' "offline support included" claims are actually comparable, instead of each one claiming the feature at very different levels of rigor — the same principle this guide's red flags apply to deep linking and push infrastructure elsewhere on the site.
What This Costs, and Its Limits
| Engagement | Range | Timeline | Typical Scope |
|---|---|---|---|
| Discovery + offline-architecture audit | $9k–$22k | 2–4 weeks | Data model and write-concurrency review, sync-engine fit assessment, conflict-resolution policy design |
| Single-platform implementation | $28k–$70k | 4–9 weeks | One platform done correctly: outbox, idempotency keys, chosen sync engine integration, background scheduling |
| Cross-platform sync engine integration | $70k–$180k | 9–16 weeks | iOS + Android + web on a shared sync engine, CRDT or app-defined conflict resolution, encrypted local storage |
| Enterprise / regulated build | $180k–$420k+ | 14–24 weeks | Audit logging of every conflict resolution, compliance-grade encryption at rest, documented data-integrity review |
One scoping note specific to this domain: the discovery phase should always include an explicit write-concurrency audit of your actual data model — which records genuinely get edited by more than one actor concurrently, and which don't — before a sync engine or conflict-resolution strategy gets chosen. Picking a CRDT-based engine for data that's effectively single-writer wastes complexity budget; picking last-write-wins for data with real concurrent edits loses user work silently.
None of this replaces testing your own sync engine choice against your actual data model, write patterns, and device population — this guide can only describe the current state of a fast-moving category as of the date it was checked, not guarantee it hasn't shifted again by the time you read it. Given how much has already changed in eighteen months, treat that as a real possibility, not a formality.
Get Your Offline-First Architecture and Sync Engine Choice Audited
Book a free 60-minute discovery call with Frenchy Digital, a senior-led Black-owned Los Angeles agency. We review your data model, write concurrency, and sync-engine fit, and send a written, fixed-price phased proposal within 5 business days.
1517 S Bentley Ave Unit 204, Los Angeles CA 90025
Frequently Asked Questions
Sources & References
- 1Ink & Switch — Local-First Software: You Own Your Data, in Spite of the Cloud↗
- 2Martin Kleppmann et al. — Local-First Software (Onward! 2019 paper, PDF)↗
- 3Ink & Switch — Peritext: A CRDT for Rich-Text Collaboration↗
- 4ACM — Peritext: A CRDT for Collaborative Rich Text Editing (PACM HCI, CSCW 2022)↗
- 5MongoDB Community Forums — Atlas Device Sync: End-of-Life and Deprecation↗
- 6MongoDB Community Forums — Update to End-of-Life and Deprecation Notice↗
- 7GitHub — realm/realm-swift Discussion #8680: "The Future: Realm Is Deprecated/Dead"↗
- 8Electric — Electric Is Joining Databricks↗
- 9Databricks Blog — Electric Joins Databricks to Bring WASM Postgres to AI Agent Sandboxes↗
- 10Neon Blog — Electric Is Joining Team Neon at Databricks↗
- 11PowerSync — A New Open Era for PowerSync↗
- 12PowerSync — Licensing & Terms↗
- 13Rocicorp — Replicache↗
- 14Rocicorp — Zero↗
- 15RxDB — Official Site↗
- 16GitHub — pubkey/rxdb↗
- 17GitHub — Nozbe/WatermelonDB↗
- 18TechCrunch — Ditto Lands $82M to Synchronize Data From the Edge to the Cloud↗
- 19Turso — Introducing Offline Writes for Turso↗
- 20Turso — Offline Sync Public Beta↗
- 21Firebase Documentation — Access Data Offline (Firestore)↗
- 22Apple Developer Documentation — BGTaskScheduler↗
- 23Apple Developer Documentation — Refreshing and Maintaining Your App Using Background Tasks↗
- 24Android Developers — Persistent Work (Task Scheduling, Background Work)↗
- 25AWS Prescriptive Guidance — Transactional Outbox Pattern↗

