Recommended Free Tools
To make a React app useful offline, treat cached server reads, durable local edits, and later synchronization as separate features. TanStack Query can manage network-aware fetching and persist its cache; IndexedDB can hold structured local records; and a service worker can cache app assets or selected request responses. None of these alone provides complete offline synchronization.
Choose what “offline” needs to mean
Start with the user outcome, because each one calls for a different data design:
- Show data the app already fetched: persist and restore TanStack Query’s cache. This can make previously loaded server data available after a reload without a connection.
- Let users make changes while disconnected: save those changes durably in a local write model, such as domain records in IndexedDB. A query cache is not a reliable substitute for that model.
- Send local changes to the server later: define a synchronization protocol: what gets replayed, in what order, how duplicates are prevented, and what happens when the server rejects or conflicts with a change.
It is useful to describe these separately in product behavior: an offline read is not proof that a draft is safely stored, and a saved draft is not proof that it has synchronized.
Choose a network mode for each workload
TanStack Query’s current documentation describes three network modes. They control how queries and mutations relate to TanStack Query’s online state; they do not provide durable storage. Select a mode based on what the query function actually does.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
| Mode | First attempt | When a request fails while offline | Good fit |
|---|---|---|---|
online (default) |
Waits for TanStack Query’s online state before running. | Queries and mutations pause while that state indicates offline. | Network-dependent work that should wait for connectivity. |
always |
Runs regardless of online state. | Does not use network state to pause work. | A query function that reads local data and does not need a network connection. |
offlineFirst |
Runs once even when offline. | After failure, retries pause while offline. | Requests that may be satisfied by a service worker or HTTP cache before a network failure occurs. |
Set the mode at the query or mutation level when workloads differ; a single global choice can be wrong for a mixed app. For example, a local IndexedDB lookup does not need the same network behavior as a request to a remote API.
In the UI, inspect fetch status as well as query status. A query can be pending while paused, so rendering every pending state as an active spinner can mislead users. Provide an appropriate offline or waiting state for work that has not started.
Persist the Query cache for restored reads
TanStack Query’s persistence mechanism restores dehydrated query and mutation state, then subscribes to cache changes so later state can be saved. It preserves Query state; it does not create an IndexedDB schema or define how application records synchronize.
Keep cache lifetime aligned with persistence lifetime
Set the QueryClient’s gcTime to at least the persistence maxAge if the goal is to retain restored cache entries for the full persistence window. The current persistence guide notes a five-minute gcTime default for hydration and a 24-hour maxAge default. Without an explicit lifetime choice, in-memory garbage collection can discard restored entries sooner than expected.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use a buster when saved state becomes incompatible
Give persisted state a build or schema identifier and change it when an incompatible deployment makes old cache data unsafe to reuse. Expired, busted, erroneous, or empty persisted state is removed by the persistence flow.
Make restoration part of startup
- Create one stable QueryClient for the app rather than a new client on each render.
- Start cache restoration through the persistence provider or an explicit restore step.
- Decide what the app displays while restoration is incomplete, especially if a route depends on restored data.
- Only then allow dependent fetches or resume persisted mutations, so an eager refetch does not race restoration without a defined rule for which result should win.
The TanStack offline example demonstrates waiting for restoration before some router fetches and resuming paused mutations afterward. That example is in the v4 documentation; verify version-sensitive names and defaults against the current v5 documentation before adopting its code.
Choose between persisting Query state and storing domain records
TanStack Query’s persistence abstraction is storage-agnostic. An IndexedDB-backed persister can save dehydrated QueryClient state, while a separate domain database can store application records and serve as the source for local query functions. These choices address different needs:
| Approach | Useful for | What the app still needs to own |
|---|---|---|
| Persist dehydrated QueryClient state | Restoring previously fetched query and mutation state between sessions. | Cache lifetime, invalidation, incompatible-state handling, and a policy for any restored paused mutations. |
| Store domain records in IndexedDB | Structured local data, offline edits, indexes, and application-controlled migrations. | Record schema, local write semantics, and the synchronization and conflict policy. |
A persister is not a domain database merely because it writes to IndexedDB. If users need durable offline edits, model those edits explicitly rather than relying on whatever happens to remain in a server-state cache.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteUse IndexedDB for structured local data
IndexedDB is an asynchronous browser database for structured records. Its versioned schema, transactions, and indexes make it suitable for local data that needs more than string-keyed storage. A typical design opens a database, creates or updates object stores during a version upgrade, and uses transactions to issue reads and writes. Add indexes for lookup patterns that would otherwise require scanning the whole store.
Rank #4
Plan schema upgrades as part of the product, not as an incidental setup step: deployed clients may have older local schemas when a new version of the app opens. Keep Query-cache persistence and domain-record migrations conceptually separate, even if both use IndexedDB under the hood.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Add a service worker for assets and selected responses
A service worker can intercept requests and serve cached responses, including app assets, when a network is unavailable. This complements Query persistence and IndexedDB rather than replacing either: service-worker caching concerns request-response and asset lifecycles, while IndexedDB supports structured records and transactions.
- Cache only resources whose offline behavior is understood, and define when cached responses should be refreshed.
- Version and retire caches deliberately. During an update, old and new service-worker versions can coexist until activation.
- Use
offlineFirstwhere a first request may be fulfilled by a service-worker or HTTP cache, but do not assume the worker caches arbitrary API data automatically.
Service workers require a secure context, generally HTTPS; localhost is treated as secure for development.
Best Value
Design offline writes as a synchronization feature
TanStack Query can restore paused mutations and resume them, but a reload requires the application to provide a mutation function again. The official offline example sets a default mutation function for restored mutations, resumes them after persistence restoration succeeds, and invalidates queries afterward. It illustrates the mechanism, not a universal synchronization policy.
Before offering queued writes, make these application decisions explicit:
- Durability and visibility: record user intent locally and show whether a change is queued, syncing, accepted, or failed.
- Duplicate protection: use idempotency keys or an equivalent server-supported mechanism so retrying a request does not accidentally apply the same operation twice.
- Retry and ordering: decide which failures are retryable and whether later operations depend on earlier ones.
- Authentication and validation: handle expired credentials and server-side validation failures without silently discarding local work.
- Conflicts: define how local and server changes are reconciled, particularly for collaborative or consequential records.
Framework and browser APIs do not prescribe one safe conflict policy for every app. The server contract and the consequences of a rejected or duplicated write determine what is appropriate.
Account for browser storage and privacy limits
Browser storage is best-effort by default: quota and eviction policies vary, users can clear site data, and private browsing can use different limits or remove stored data when the session ends. An application can request stronger retention with navigator.storage.persist(), but browser behavior varies: a request may be approved automatically, prompt the user, or be denied. Do not promise permanent local data.
Do not store secrets or sensitive records without a deliberate threat model and retention plan. Define what happens to local data at logout or tenant change, and keep server authorization checks in place; local cleanup does not replace authorization.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




