
Building a Really Snappy Offline-First Flow with TanStack Query
A common issue with modern software is the network call. This can be a problem in two ways. First, these calls can be slow, creating a mediocre user experience. The second problem is that these calls can fail when we are having network problems. So I thought, can we run our UI off an offline version of the data.
I went all-in on an offline-first, cache-forward approach using TanStack Query.
Why is this valuable?
Instead of treating the backend as the single source of truth, I treat the TanStack cache that way, at least from the UI’s perspective. The local cache basically becomes the source of truth for the UI, and the server catches up whenever it can.
It’s been one of those “damn, this actually feels good” moments.
The Local Cache as UI State
Here is roughly how it’s wired up right now.
User drags a task? Optimistic update, cache changes, and the UI paints instantly.
User edits a project name? Same deal.
It is cache first, network later, or never, if they are offline. I give the data quite a long life so the app still feels fast even after being backgrounded for hours:
The Mutation Queue
The mutation queue is doing the heavy lifting. I don’t just fire mutations and hope.
There is a little queue living in IndexedDB (via idb-keyval in my case). When the user does something, we follow a simple sequence:
- Optimistic update to the cache.
- Enqueue the mutation, which includes the operation, payload, and temporary id stuff.
- The UI is already happy.
Then, a background OfflineSyncService watches navigator.onLine, periodic timers, and connectivity changes. When it thinks it has a shot, it flushes batches per project.
The flush looks roughly like this:
The Payoff
Because I’m using queryClient.setQueryData to merge the server’s canonical versions back in, the UI just updates. There is no manual invalidation, and no weird “refresh to see changes” flows.
What happens under varying network conditions?
- Offline? Everything still works, moves feel instant, and changes stick around.
- Spotty connection? Optimistic updates combined with queued retries means it feels native.
- Back online? The server wins on conflicts, and the UI smoothly updates.
It’s honestly the closest I’ve gotten to that local-first vibe while still having a proper cloud backend.
Anyone else deep in the TanStack and offline trenches right now? Curious what trade-offs you ended up making.


