An application built and tested on office wifi encodes an assumption in every screen: that a request sent will receive a response. Field agents in Kalimantan discover this assumption is false, and they discover it by losing an afternoon of work.

The instinct is to treat this as a bug to patch. It is not. It is a consequence of a decision made at the start.

Why it cannot be bolted on

Online-first applications put the server in the middle of every interaction. The server holds truth, validates, assigns identifiers and resolves ordering. Screens are views onto a remote state.

Offline-first inverts that. The device holds a real local database. Writes succeed locally and immediately. Sync is a background reconciliation between two authorities that have both moved on.

That inversion touches identity generation, validation placement, conflict semantics and every screen's understanding of what “saved” means. Retrofitting it is not adding a cache — it is rewriting the data layer and revisiting every screen built on top of it.

Three decisions that determine everything

1. Who generates identifiers

If the server assigns IDs, the device cannot create a record offline without inventing a temporary one and reconciling later — which leaks into every foreign key. Client-generated UUIDs remove the problem entirely. Make this decision on day one; it is expensive on day ninety.

2. What happens when both sides changed

Two people edited the same record while one was offline. Your options: last write wins, first write wins, merge by field, or queue for human resolution. The wrong answer is having no answer, because the default then becomes silent data loss.

Different records deserve different rules. A status field can take last-write-wins. A ledger entry cannot.

3. What the user is told

Users tolerate offline behaviour well when it is legible. Show what is pending, what has synced, and what needs attention. A spinner that never resolves produces far more support calls than an honest “3 items waiting to sync.”

The goal is not to hide that the device is offline. It is to make offline feel like a normal state rather than a failure.

Test the actual conditions

Airplane mode is the easy case, and it is not the one that breaks applications. The hard case is a weak connection: requests that take forty seconds and then fail, packets that arrive out of order, a connection that drops mid-upload.

Use network throttling profiles in CI, and test on real hardware with a real degraded connection before release. A device that believes it is online but cannot complete a request is where the worst bugs live.

The cost

Offline-first is more work up front — meaningfully more. For an internal tool used at head office, it is not worth it.

For anything used in the field, in a warehouse, or by customers outside major cities, it is the difference between an application people rely on and one they work around.