Offline-first is a product decision before it is a technical one
Sync libraries will solve the mechanics. They will not tell you what should happen when two people edit the same record on a train. That part is yours.
Every offline-capable app eventually faces the same question: two devices changed the same thing while disconnected, and now they have both reconnected. The library you chose will happily resolve this for you, usually by picking whichever write arrived last. That is a product decision being made by a default.
Decide the conflict rules with the business
For a fitness app, last-write-wins on a workout log is fine — the stakes are low and the user is usually the only editor. For a field-service app where a technician and a dispatcher can both edit a job, silently discarding one of them is a serious failure that will surface as a complaint months later.
We run a short workshop on exactly this before any sync code is written: for each entity, who can edit it, how likely a conflict is, and what the business wants to happen when one occurs. It takes an afternoon and it prevents an entire category of bug.
Design the degraded state, do not inherit it
- Every screen needs a designed offline appearance, not a spinner that never resolves.
- Queued actions should be visible. Users tolerate delay; they do not tolerate uncertainty about whether something happened.
- Failed syncs need a path back — a retry the user can trigger, and an explanation they can understand.
- Test on a genuinely bad connection, not on airplane mode. Intermittent is much harder than absent, and much more common.
The payoff
On the last app we built this way, check-in failures in poor-signal venues went from a weekly support theme to effectively zero. None of that came from the sync library, which was doing its job the whole time. It came from having decided, in advance, what should happen when things go wrong.
