All writing
Architecture·2 min read·Nov 18, 2025

In Offline-First Apps, Sync Is the Product

Building a field platform that had to work with no signal taught me that the UI is the easy half — and that sync bugs never announce themselves.

Offline-firstSyncDistributed

We built a platform for distributors who work in places with no reliable signal. They enrol in incentive programmes, photograph what they've done, and submit forms — often hours before their phone next sees a network.

The brief described an app. What we actually had to build was a small distributed system.

The moment two truths exist

Offline-first sounds like a resilience feature: the app keeps working when the network doesn't. That framing is comfortable and wrong.

The moment a device can accept a write without reaching the server, you have two copies of the truth that are permitted to disagree. Everything difficult follows from that single fact — ordering, conflict resolution, idempotency, what "saved" means in the interface, and what you do when the same record was edited in two places on the same afternoon.

The UI is the easy half. The reconciliation logic is the product.

Sync bugs don't page you

A crash reports itself. A sync bug produces data.

Specifically it produces plausible data: a submission counted twice, an edit silently overwritten by an older one that synced later, a form that a distributor is certain they completed and the system is certain they didn't. Nothing throws. Nothing alerts. Someone gets paid on it.

This changes what testing means. You are not looking for exceptions; you are looking for divergence. That means tests that take two clients, let them drift deliberately, reconnect them in both orders, and assert that the result is the same — and that it's the right same.

What I'd carry into the next one

Make every write idempotent, with a client-generated ID. Retries are not an edge case in this world; they are the normal path. A write that can't be safely repeated will be repeated anyway.

Version records, don't just timestamp them. Device clocks are wrong, sometimes by hours, and last-write-wins on a wrong clock is a data-loss policy with a friendly name.

Decide conflict policy per field, not per record. "Two people edited this submission" is usually not a real conflict — they edited different parts of it. Record-level conflict resolution turns a non-event into a user-facing dilemma.

Show sync state honestly. Users tolerate "not yet synced" easily. What destroys trust is an interface that says saved when it means queued.

Every one of those is a decision about the data model, made before anyone opens a design file. That's the actual lesson: offline-first is a constraint you architect around, not a mode you switch on.