Learning TanStack Query - Persistence & Offline
Episode 12 of 23

Learning TanStack Query - Persistence & Offline

This episode covers persisting the cache to localStorage or IndexedDB using the sync-storage and async-storage persisters, as well as offline behavior: onlineManager, exponential backoff retry, and handling disconnected networks.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Modern web applications must be resilient on bad networks. When a user opens the application without a connection, the TanStack Query cache that only lives in memory will be empty, and the page will show a never-ending loading state. The solution: persistence — saving the cache to durable storage such as localStorage or IndexedDB, so data is available when the application is opened again.

Episode 12 covers two things: how to save the cache with a persister, and how TanStack Query behaves when the network disconnects — including onlineManager and retry with increasing delays.

Persisting the Cache

Sync vs Async Storage Persister

TanStack Query provides two official persisters: query-sync-storage-persister for localStorage and query-async-storage-persister for async storage such as IndexedDB.

Install the persister
npm install @tanstack/react-query-persist-client @tanstack/query-sync-storage-persister

npm install @tanstack/react-query-persist-client adds PersistQueryClientProvider, and the second persister package provides createSyncStoragePersister.

Installing PersistQueryClientProvider

After installing, replace QueryClientProvider with PersistQueryClientProvider:

JSPersist the cache to localStorage
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"
 
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,
      gcTime: 1000 * 60 * 60 * 24,
    },
  },
})
 
const persister = createSyncStoragePersister({
  storage: window.localStorage,
})
 
function App() {
  return (
    <PersistQueryClientProvider
      client={queryClient}
      persistOptions={{ persister }}
    >
      <Todos />
    </PersistQueryClientProvider>
  )
}

createSyncStoragePersister uses window.localStorage as the storage medium. Note the extended gcTime — a persisted cache is only restored if it hasn't passed gcTime, so set gcTime longer than a short session. PersistQueryClientProvider automatically saves the cache to localStorage and restores it when the application is opened.

IndexedDB for Large Caches

localStorage is limited to about 5 MB and is synchronous. For large caches, use the async persister with IndexedDB:

JSPersist the cache to IndexedDB
import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister"
import { IDBStorage } from "idb-keyval"
 
const persister = createAsyncStoragePersister({
  storage: IDBStorage,
})

createAsyncStoragePersister works with async Storage implementations like idb-keyval. IDBStorage from idb-keyval stores data asynchronously, which is better suited for large caches because it doesn't block the main thread.

Offline and Retry

Behavior When Offline

When the network disconnects, failed queries don't give up immediately. TanStack Query uses retry with increasing delays — exponential backoff. Each failed attempt increases the delay before the next attempt, up to the retry limit.

JSExplicit retry configuration
useQuery({
  queryKey: ["todos"],
  queryFn: fetchTodos,
  retry: 3,
  retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30 * 1000),
})

retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30 * 1000) produces delays of 1 second, 2 seconds, 4 seconds, then caps at 30 seconds. retryDelay accepts a function to compute the delay per attempt — a built-in exponential backoff pattern without any extra library.

onlineManager

TanStack Query tracks the online status through onlineManager. When the connection returns, failed queries that are still within their retry window are automatically retried:

JSConfiguring onlineManager
import { onlineManager } from "@tanstack/react-query"
 
onlineManager.setOnline(navigator.onLine)
window.addEventListener("offline", () => onlineManager.setOnline(false))
window.addEventListener("online", () => onlineManager.setOnline(true))

onlineManager.setOnline tells the library the current connection status. onlineManager internally decides when retries resume — when online is set back to true, pending requests are woken up again. By default, TanStack Query uses the browser's built-in online/offline events.

Avoiding Stacked Retries

For queries that genuinely shouldn't be retried — for example a wrong user input — shrink the retry value or use the error status to show a message immediately. A common combination: retry: 1 for read queries, and the default retry for idempotent operations.

Warning

Sensitive data persisted to localStorage can be read by other scripts on the same origin. Don't persist tokens or personal data without security consideration — episode 15 will discuss this practice more deeply.

Closing

Episode 12 made your application resilient: a cache that survives through localStorage or IndexedDB, reasonable offline behavior, and retry with exponential backoff controlled by onlineManager. This combination keeps the application useful even when the network isn't friendly.

Key takeaways:

  • PersistQueryClientProvider saves the cache to persistent media.
  • Sync persister for localStorage; async persister for IndexedDB.
  • Extended gcTime so the cache can be restored across sessions.
  • Default retry uses exponentially increasing delays.
  • onlineManager controls behavior when the connection returns.
  • Sensitive data should not be stored raw in localStorage.

In the next episode, episode 13, we will discuss performance and cache tuning — reducing excessive fetches with the right staleTime, keepPreviousData, and cache normalization, plus Suspense mode with useSuspenseQuery for granular loading integrated with React Suspense.

Learning TanStack Query - Persistence & Offline | Learning TanStack Query