Persist Query Client

Purpose and Scope

Persisting a Query Client means saving TanStack Query cache state outside the in-memory runtime so an application can restore useful server-state data after a reload, tab restart, or offline interruption. The central object is still the QueryClient: it owns the query cache, mutation cache, defaults, invalidation APIs, and paused mutation resumption behavior described in the public QueryClient reference. Persistence adds a storage boundary around that client rather than replacing normal query behavior. After restore, components continue to read through framework APIs such as useQuery or Angular injectQuery, and background refetching can reconcile restored data with the server.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/guides/background-fetching-indicators.md

This page focuses on the application-level contract: create one QueryClient, provide it through the framework adapter, connect it to a persister, restore cached state before or during app startup, and decide how old or incompatible persisted data should be discarded. The supplied Angular documentation shows the same foundational pattern used by persistence providers: a QueryClient is created and registered with framework providers through provideTanStackQuery, while optional features are layered onto that setup. Persistence providers in React and Preact follow the same architectural idea, but expose framework-native provider components instead of Angular providers.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/devtools.md

Relevant Source Files

  • docs/framework/angular/reference/functions/provideTanStackQuery.md - Defines the Angular entry point for registering a QueryClient and optional query features, which is the same application boundary where persistence must be wired in for Angular-style apps.
  • docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md - Explains that TanStack Query is promise-based and data-client agnostic, and contrasts Angular HttpClient SSR request caching with TanStack Query hydration setup.
  • docs/framework/angular/devtools.md - Shows feature composition with provideTanStackQuery(new QueryClient(), withDevtools()), a useful model for understanding how additional runtime behavior is layered around a client.
  • docs/framework/angular/guides/background-fetching-indicators.md - Demonstrates restored or cached data being observed through query state such as isFetching, plus global fetching state through injectIsFetching.
  • docs/config.json - Shows the documentation site structure and framework sections that host Query guidance across React, Solid, Vue, Svelte, Lit, and Angular.
  • docs/community-resources.md - Lists community utilities and learning resources, including cache-key and generated-client tooling that often pairs with persistent cache strategies.

Core Primitives

The primitive to preserve is the QueryClient. It is the cache-facing API used to prefetch, read, write, invalidate, remove, reset, and clear query data, and it can also resume paused mutations. A persistence layer serializes selected client state, stores it through a persister, and later hydrates a compatible client instance with that saved state. That restored state is not a replacement for query functions: queries still need keys, fetching functions, stale-time policy, retry policy, and network behavior so restored data can be treated as fresh, stale, or ready for background synchronization.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md

A persister is the storage adapter. In browser applications, synchronous storage is commonly used for localStorage-style persistence, while asynchronous storage is used for IndexedDB, React Native storage, or other promise-based backends. The provider or restore utility connects the persister to a QueryClient and controls how restore and save operations happen. The important design constraint is that persistence should be attached once at the same level as the long-lived client. Creating a new client per render or per route can fragment the cache and make restored state difficult to reason about.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md

A dehydrated cache is a portable snapshot of query and mutation state. Hydration is the reverse operation: load the snapshot into a fresh client. The Angular data-fetching guide makes an important distinction for server-rendered applications: Angular HttpClient has its own SSR request cache, while TanStack Query has its own hydration functionality that can be more powerful but requires setup. Persistence is closer to hydration than to request interception; it saves Query state after it has passed through Query’s cache lifecycle, rather than caching raw HTTP responses at the transport layer.

Sources: docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md

Setup Flow

Start by creating a stable client and passing it into the adapter-level provider. In Angular, the documented setup is provideTanStackQuery(new QueryClient()), and the reference also supports providing a QueryClient through an InjectionToken for lazy loaded routes. In React persistence setups, the analogous boundary is a persistent provider component that receives the client and persistence options. In both cases, the application should avoid scattering independent clients across the component tree unless it intentionally wants isolated caches, because persistence works best when one cache represents the user-visible application state.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md

import { QueryClient, provideTanStackQuery } from '@tanstack/angular-query-experimental'
 
export const appConfig = {
  providers: [provideTanStackQuery(new QueryClient())],
}

Next, attach persistence as part of application startup. For React and Preact, the usual shape is a framework provider such as PersistQueryClientProvider that wraps the normal Query client context and delays or coordinates restore before queries begin normal work. The provider receives the queryClient plus persistence options such as the storage persister, maximum age, cache buster, dehydration options, hydration options, and optional retry strategy. In Angular-style configuration, the source evidence shows optional features being passed to provideTanStackQuery, as with withDevtools, so the mental model is the same: one client, one framework integration point, and opt-in behavior layered around that client.

Sources: docs/framework/angular/devtools.md, docs/framework/angular/reference/functions/provideTanStackQuery.md

const queryClient = new QueryClient()
 
<PersistQueryClientProvider
  client={queryClient}
  persistOptions={{
    persister,
    maxAge: 1000 * 60 * 60 * 24,
    buster: 'v1',
  }}
>
  <App />
</PersistQueryClientProvider>

Restore Behavior and UI State

A restored cache can immediately give components data, but the restored data still participates in Query’s freshness model. If restored data is stale, observers can show cached content while a background refetch runs. The Angular background fetching guide demonstrates this distinction in UI terms: a component can render successful data and also display a Refreshing... indicator when the query is fetching, while a global indicator can be driven by injectIsFetching. That is the user experience persistence is meant to preserve: instant continuity from stored state, followed by normal cache validation.

Sources: docs/framework/angular/guides/background-fetching-indicators.md

Restore timing matters because queries may mount before saved cache state is available. Persistent provider bindings are designed to coordinate that startup phase so components do not immediately overwrite useful stored data with avoidable loading states or duplicate fetches. Once hydration finishes, normal observers decide whether to fetch based on options such as staleTime, enabled, networkMode, retry, refetchOnWindowFocus, and refetchOnReconnect. The persisted snapshot should therefore be treated as an initial cache source, not as an instruction to stop refetching forever.

Sources: docs/framework/angular/guides/background-fetching-indicators.md, docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md

For SSR, distinguish three related but separate mechanisms. Server rendering can dehydrate Query state into the HTML response for first paint. Angular HttpClient can cache server-side HTTP requests and prevent unnecessary client requests without using TanStack Query hydration. Client persistence can then save query state after runtime activity so it survives later reloads. These mechanisms can complement each other, but they have different ownership boundaries: request transport caching belongs to the fetching client, hydration belongs to the Query cache snapshot, and persistence belongs to long-term storage across browser or app sessions.

Sources: docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md

Retry, Expiration, and Invalidation Strategy

Persistence should be conservative about old or incompatible data. A maxAge policy prevents very old cache snapshots from being restored, while a buster value gives the application a manual version switch for schema, authentication, or API changes. If the stored cache is expired or busted, the app should discard it and let normal query execution repopulate the cache. This is especially important for generated clients, GraphQL schemas, or standardized query-key factories listed in community resources: when the meaning of a key or response shape changes, a previous persisted value can become misleading even if it is still syntactically valid.

Sources: docs/community-resources.md

Retry strategy has two meanings in persistence workflows. Query retries handle failed network requests during normal fetching, as part of the QueryClient and query option model. Persistence retries handle failures while restoring or saving a cache snapshot, for example when storage is full or a stored snapshot contains too much data. A practical persistence retry strategy can remove the oldest or least valuable cached queries and try saving again, or it can stop persisting and allow the in-memory cache to continue. This keeps storage failure from becoming application failure.

Sources: docs/framework/angular/guides/background-fetching-indicators.md

Mutation persistence needs extra care because mutations can have side effects. The QueryClient public surface includes paused mutation resumption, and persistent clients are commonly used with offline workflows where a mutation is queued while disconnected and resumed when the app is online again. To make that safe, define mutation defaults and mutation functions that are stable after a reload, keep variables serializable, and make server operations idempotent where possible. A restored paused mutation should represent a deliberate workflow, not an accidental replay of an outdated user action.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md

Compact Reference

ConceptPublic contractNotes
QueryClientLong-lived cache owner passed to framework provider bindingsCreate once and reuse across the app boundary.
PersistQueryClientProviderFramework provider that combines Query context with restore/persist behaviorCommonly used by React and Preact persistence bindings.
persistQueryClientCore-style utility for connecting a QueryClient to persistence optionsUseful when a framework provider is not the right lifecycle boundary.
persisterStorage adapter with persist, restore, and remove behaviorChoose sync or async storage based on runtime needs.
maxAgeMaximum age for restored cache snapshotsDiscard old persisted state instead of showing stale historical data indefinitely.
busterApplication-controlled cache version stringChange it when response shapes, auth scope, or key semantics change.
dehydrateOptions / hydrateOptionsControls what is saved and how it is restoredUse to limit persisted data and align with SSR hydration choices.
persistence retry strategyHandles storage restore or save failuresCan remove old queries, retry persistence, or abandon persistence while keeping runtime cache behavior.

Next Steps

When adding persistence, first confirm the normal Query setup works without storage: queries should have stable keys, promise-returning query functions, clear invalidation paths, and useful background fetching indicators. Then attach a persister at the same application level as the QueryClient, choose expiration and buster policies, and test reload, offline, storage-full, logout, and schema-change scenarios. For Angular applications, use the documented provideTanStackQuery pattern as the provider boundary and compare TanStack Query hydration needs against Angular HttpClient SSR caching before layering on long-term persistence.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md, docs/framework/angular/guides/background-fetching-indicators.md