July 11, 2026
"Offline-first" usually means "we cached the list". That is the easy half, and it is not the half that generates support tickets.
The hard half is writes: what happens when someone edits a record on a train, force-quits the app, and reopens it two days later on a different network.
Three pieces, and they have to be separate:
If any of the three lives in React state, you will lose writes and you will not find out until a user tells you.
Last-write-wins is the default and it is usually wrong, because it silently discards work. Pick per-table, and write the rule down:
Supabase does not decide this for you, and no library will either. It is a product question wearing an engineering costume.
Helps: Postgres means real constraints, so a bad merge fails loudly at the database rather than corrupting quietly. Row-level security means the reconciler cannot be tricked into writing someone else's row even if the client is compromised. Realtime gives you invalidation without polling.
Does not help: there is no built-in sync engine. The outbox and the reconciler are yours to write. Anyone telling you otherwise has built a demo, not an app.
Airplane mode, make five edits, force-quit from the app switcher, reopen, come back online. Every edit should land, in order, exactly once.
Almost no offline implementation passes that on the first try. Mine did not.
I have built this loop for a real product. The case study is here, and if you are staring down the same problem, say hello.