Users expect mobile apps to work on an aeroplane, in a basement, or on a spotty connection. Building for that expectation — not as an afterthought but as a core design constraint — is what offline-first means. It's a significantly different architectural approach and one that pays dividends far beyond the offline use case itself.
Local Database as the Source of Truth
In a traditional online-first app, the server is the source of truth and the UI reads directly from API responses. In an offline-first app, the local database is the source of truth and the server is a remote replica. All reads come from local storage; all writes go to local storage first, then sync to the server in the background. This inversion means the app responds instantly to user actions regardless of connectivity.
For React Native, SQLite (via expo-sqlite or op-sqlite) and WatermelonDB are the primary choices for a local relational store. WatermelonDB is optimised for large datasets with lazy loading and observable queries, which makes it particularly well-suited for apps with complex data models.
Sync Strategy: When, What, and How Much
Sync strategy is where the real design work happens. Full sync — download everything — is simple but doesn't scale as data grows. Incremental sync — download only changes since the last sync — is efficient but requires a reliable change tracking mechanism on the server (updated_at timestamps are a start; a dedicated change log is more robust). Selective sync — download only data relevant to the current user or context — is the most complex but the right choice for large, multi-tenant datasets.
Conflict Resolution Is Unavoidable
When the same record is modified on the client while offline and on the server simultaneously, you have a conflict. You cannot avoid this — you can only decide how to handle it. Last-write-wins (the most recent timestamp wins) is simple but can silently discard changes. Operational transforms are powerful but complex. For most business apps, the pragmatic approach is: last-write-wins for most fields, with explicit merge logic for fields where both changes should be preserved (like comment threads or lists). Build conflict resolution into your data model design, not as a patch on top of it.