Skip to content
SOFTRIZQSoftrizq home
All writing
5 min readMobileArchitecture

Offline-first is an architecture, not a feature

You cannot add offline support to an online-first app. We learned why while building a tool for children who cannot rely on a connection.

Offline support usually arrives on a roadmap as a feature, somewhere between push notifications and dark mode. It is not a feature. It is a decision about where the source of truth lives, and it is close to impossible to reverse.

We built a Flutter support tool for autistic children and their caregivers: communication aids, emotion learning, predictable routines, and progress a caregiver can actually read. Offline was not a nice-to-have. A loading spinner in the middle of a routine does not degrade the experience — it breaks the predictability the entire product exists to provide.

The two architectures

In an online-first app, the server holds the truth. The client asks for data, renders it, and shows a spinner while it waits. Every screen implicitly assumes a network.

In an offline-first app, the device holds the truth. The client reads and writes locally and always succeeds. Synchronisation is a background concern that reconciles two copies of reality, and it is allowed to be slow, or fail, or happen tomorrow.

Retrofitting the second onto the first is not a refactor. It is a rewrite of every screen that reads or writes anything.

What changes when the device owns the truth

The obvious change is a local database. The less obvious changes are the ones that catch teams out.

Identifiers have to be generated on the device

If the server allocates IDs, nothing can be created offline. That single constraint forces a client-generated scheme, and it has to be collision-safe across devices that have never spoken to each other.

Every write needs a conflict answer

Two devices edit the same record while both are offline. What happens? Last-write-wins is a decision, not a default — and for anything a caregiver has deliberately configured, silently discarding their change is the wrong answer. You have to decide this per data type, before you build.

The UI can never show a spinner for local data

If reading is local, it is instant, and any loading state you write is a lie that will flash on screen. Removing them later means touching every screen.

What we got for it

  • The app opens and works instantly, with no network and no perceptible delay.
  • A dropped connection mid-session changes nothing the user can see.
  • Battery and data use fall, because sync is batched rather than constant.
  • Testing gets simpler — most of the app has no network dependency to mock.

The honest cost

Sync is genuinely hard, and it is where the difficulty concentrates rather than disappears. You are maintaining a distributed system with an unbounded number of unreliable replicas, and you own every conflict rule.

You also cannot revoke data instantly. If something must disappear from a device the moment somebody says so, offline-first fights you, and that is a real constraint worth naming before you commit.

We do this on client projects too

Web applications, applied AI, and learning platforms — documented the way this article describes.

Book a call