Skip to content

TanStack Query prompt

Rules that make your coding agent use TanStack Query v5 as it works today: queryOptions shared by server and client, a deliberate staleTime, mutations that invalidate, and hydration in Next.js 16.

What it makes your agent good at

Agents mix TanStack Query v4 and v5: cacheTime, keepPreviousData: true, onSuccess on queries, isLoading for first loads. They create a QueryClient per render or share one across server requests, and add a client cache where a Server Component could simply read the data. This prompt gives them v5 as it is: one queryOptions per query, keys that hold every input, mutations that invalidate or roll back, and hydration that follows the Next.js 16 docs.

  1. One queryOptions per queryKeys and fetchers defined once and reused by useQuery, useSuspenseQuery, prefetchQuery and a typed getQueryData.
  2. Keys that hold every inputIds, filters, page and sort in array keys, invalidated by prefix after each write, never concatenated strings.
  3. staleTime set on purposeA default on the QueryClient with per-query overrides, and gcTime treated as memory, not freshness.
  4. Mutations that roll backOptimistic UI from variables, or onMutate with cancelQueries, a snapshot and a rollback in onError.
  5. Next.js 16 hydrationA client per request on the server, prefetch in Server Components, HydrationBoundary and the Cache Components caveats.
  6. Knows when to skip itData a Server Component can render is read on the server, and query data is never copied into state or effects.

The TanStack Query rules

134 lines, current to TanStack Query 5, React 19.3 and Next.js 16.4. Last reviewed October 23, 2026. Plain Markdown, no front matter: each agent's is below.

Download

tanstack-query.md

# TanStack Query

You are working in a React 19 project with TypeScript and TanStack Query v5 (`@tanstack/react-query`). Write code for the version in `package.json`: v5 renamed or removed options that most older examples still use.

## Before you write code

- Check the `@tanstack/react-query` major version (these rules are v5), whether `@tanstack/react-query-devtools` and `@tanstack/eslint-plugin-query` are installed, and where the project creates its `QueryClient` and keeps its query options. Extend those; don't add a second provider.
- In a Next.js App Router project, read `node_modules/next/dist/docs/01-app/02-guides/client-side-data-fetching/` (`index.md` and `tanstack-query.md`): providers, hydration and Cache Components for the installed Next.js.

## When to use it

- Use it for server data that Client Components share, refetch, paginate, poll, update optimistically or load after an interaction (search as you type, infinite lists, live dashboards).
- Don't use it for data a Server Component can read once and render: await it on the server. When a Client Component needs server data once, pass it a promise from the Server Component and read it with `use()`.
- The query cache owns server data: never copy it into `useState`, context or a global store, and never fetch in `useEffect`.

## Query options and keys

Define each query once with `queryOptions` (or `infiniteQueryOptions`) in a module the server and the client can both import, and reuse that object everywhere:

```ts
import { queryOptions } from "@tanstack/react-query";

export const todoKeys = {
  all: ["todos"] as const,
  list: (filters: TodoFilters) => [...todoKeys.all, "list", filters] as const,
  detail: (id: string) => [...todoKeys.all, "detail", id] as const,
};

export const todoQuery = (id: string) =>
  queryOptions({
    queryKey: todoKeys.detail(id),
    queryFn: async ({ signal }): Promise<Todo> => {
      const res = await fetch(`/api/todos/${id}`, { signal });
      if (!res.ok) throw new Error(`Todo ${id}: ${res.status}`);
      return res.json();
    },
  });
```

- Keys are arrays from general to specific and hold every input the query function reads: ids, filters, page, sort, and the user or tenant when the response depends on it. Object order inside a key doesn't matter.
- The same options go to `useQuery`, `useSuspenseQuery`, `prefetchQuery`, `ensureQueryData` and `getQueryData(todoQuery(id).queryKey)`, which is then typed.
- `fetch` doesn't reject on 4xx or 5xx: check `res.ok` and throw. Pass `signal` so cancelled queries abort. Validate untrusted responses with a schema.

## Reading data

- `useQuery` when the component renders its own states. `isPending` means no data yet; `isFetching` means a request is in flight, including background refetches. Keep data on screen during a refetch and show a quiet indicator.
- `useSuspenseQuery` when a `<Suspense>` boundary owns loading and an error boundary owns errors: `data` is always defined, and `enabled` and `placeholderData` don't exist there. Several suspense queries in one component run one after another: split them into siblings or use `useSuspenseQueries`.
- `staleTime` (default 0) decides when data refetches on mount, window focus and reconnect. Set a default on the `QueryClient` (such as 60 seconds) and override it per query. `gcTime` (default 5 minutes) is how long unused data stays in memory, not freshness.
- `select` derives what one component needs from the cached response; keep the function stable (module scope) when it is expensive.
- `placeholderData` is shown but never cached: use it for a preview, such as the row from a list. `initialData` is written to the cache and counts as fresh for `staleTime`: use it only for complete data.
- Polling: `refetchInterval` (milliseconds, or a function of the query that returns `false` to stop). It pauses in background tabs unless `refetchIntervalInBackground` is set.
- Prefetch on intent: `queryClient.prefetchQuery(todoQuery(id))` on a link's hover or focus.
- Dependent queries: `queryFn: userId ? () => fetchUser(userId) : skipToken` (typed), or `enabled: Boolean(userId)`. Separate `useQuery` calls already run in parallel; for a dynamic list use `useQueries({ queries: ids.map((id) => todoQuery(id)) })` with `combine`.
- Pages: `placeholderData: keepPreviousData` keeps the last page on screen; use `isPlaceholderData` to disable Next and dim the list, and `prefetchQuery` the next page.
- Infinite lists: `useInfiniteQuery` with `initialPageParam` (required) and `getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined` (`undefined` or `null` ends the list). Render `data.pages.flatMap(…)`, call `fetchNextPage()` only when `hasNextPage && !isFetchingNextPage`, and cap long feeds with `maxPages`.

## Mutations

- `useMutation({ mutationFn, onSuccess, onError, onSettled })`; its pending flag is `isPending`. Prefer `mutate` with callbacks; `mutateAsync` only where you await it and catch.
- After a write, invalidate what changed: `queryClient.invalidateQueries({ queryKey: todoKeys.all })` matches by prefix. Return that promise from `onSettled` so the mutation stays pending until fresh data arrives. When the response is the new entity, `setQueryData` it instead.
- Simple optimistic UI: render `variables` while `isPending` (in another component, read them with `useMutationState` and a `mutationKey`). Nothing to roll back.
- Optimistic cache update with rollback:

```ts
const queryClient = useQueryClient();
const save = useMutation({
  mutationFn: updateTodo,
  onMutate: async (next: Todo) => {
    const { queryKey } = todoQuery(next.id);
    await queryClient.cancelQueries({ queryKey });
    const previous = queryClient.getQueryData(queryKey);
    queryClient.setQueryData(queryKey, next);
    return { previous };
  },
  onError: (_error, next, snapshot) => {
    queryClient.setQueryData(todoQuery(next.id).queryKey, snapshot?.previous);
  },
  onSettled: (_data, _error, next) =>
    queryClient.invalidateQueries({ queryKey: todoQuery(next.id).queryKey }),
});
```

- Update cache entries immutably. A `mutationFn` can call a Server Function; when nothing on screen reads that data from the cache, a plain form action with `useActionState` is simpler.

## Errors and retries

- Queries retry 3 times with backoff in the browser and not at all on the server. Don't retry client errors (here `HttpError` is your fetch wrapper's error): `retry: (count, error) => count < 3 && !(error instanceof HttpError && error.status < 500)`.
- `useQuery` has no `onSuccess`, `onError` or `onSettled` in v5. For app-wide error toasts, pass `queryCache: new QueryCache({ onError })` to the `QueryClient`; for local errors, render `error`.
- `throwOnError` sends errors to the nearest error boundary. Before retrying from that boundary, reset queries with `QueryErrorResetBoundary` or `useQueryErrorResetBoundary().reset`, or the cached error is thrown again.
- When your errors aren't `Error`, register the type once: `declare module "@tanstack/react-query" { interface Register { defaultError: HttpError } }`.

## Server rendering (Next.js App Router)

- One `QueryClient` per server request and one per browser tab. Never share a module-level client on the server (users would see each other's data), and never `new QueryClient()` in a component body.

```tsx
"use client";

import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ReactNode } from "react";

const makeClient = () =>
  new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } } });
let browserClient: QueryClient | undefined;
const getClient = () => (isServer ? makeClient() : (browserClient ??= makeClient()));

export function QueryProvider({ children }: { children: ReactNode }) {
  return <QueryClientProvider client={getClient()}>{children}</QueryClientProvider>;
}
```

- Render the provider in the nearest layout that needs it. To prefetch, create a `QueryClient` in the Server Component (shared across one request's components with `cache(() => new QueryClient())`), call `prefetchQuery({ ...todoQuery(id), queryFn: () => getTodo(id) })` (on the server call the data layer directly: a relative URL only resolves in the browser), and wrap the Client Component in `<HydrationBoundary state={dehydrate(queryClient)}>`. The client reads the same options. Keep `staleTime` above 0 so hydrated data isn't refetched at once.
- To stream instead of block, don't await `prefetchQuery`, and dehydrate pending queries with `shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending"` inside a `<Suspense>` boundary.
- `prefetchQuery` never throws; use `fetchQuery` when a failure should become `notFound()` or the error page.
- With `cacheComponents` on, keep queries the first render needs inside `<Suspense>`. `dehydrate()` reads the current time, which fails while prerendering cached segments: use the guide's prerenderable hydration helper. Keep each query key and its cache tag in one shared module; after a write through a Server Function, call `updateTag(tag)` there and `invalidateQueries` on the client.

## Devtools and lint

- Render `ReactQueryDevtools` from `@tanstack/react-query-devtools` inside the provider; it is left out of production builds by default.
- With `@tanstack/eslint-plugin-query` installed, keep its recommended rules on (`exhaustive-deps` for keys, `stable-query-client`).

## Do not

- Do not write v4: `cacheTime` (now `gcTime`), `keepPreviousData: true`, `useErrorBoundary` (now `throwOnError`), `isLoading` to mean "no data yet" (now `isPending`), `onSuccess` on queries, positional `useQuery(key, fn)` or `invalidateQueries(key)`, or imports from `react-query`.
- Do not pass new arguments to `refetch()`: change the key.
- Do not build keys by string concatenation, or leave out a variable the query function uses.
- Do not use `useQuery` for writes or `useMutation` for reads.

## Before you finish

- Run the type checker and the linter.
- In the devtools, check every key, that `staleTime` stops refetch loops, and that each mutation invalidates the right prefix.
- Throttle the network and check loading, error, retry, empty and background-refetch states; force a failed mutation and watch it roll back.
- With server rendering: no refetch right after hydration, no hydration warnings, and no client shared between requests.

10,399 characters, 1,449 words. Under Windsurf's 12,000-character limit for a rule, as every prompt here is.

Use it in your agent

The same rules, where your agent reads them. Each file has the front matter its agent needs, set to load when the task fits.

Claude Code

A rule it loads every session

  1. Save it as .claude/rules/tanstack-query.md in your project, front matter included, or run the install command.
  2. Claude Code loads every file in .claude/rules with your CLAUDE.md when a session starts.
  3. Prefer one file? Paste it into CLAUDE.md under its own heading instead.

.claude/rules/tanstack-query.md

# TanStack QueryYou are working in a React 19 project with TypeScript and TanStack Query v5 (`@tanstack/react-query`). Write code for the version in `package.json`: v5 renamed or removed options that most older examples still use.## Before you write code- Check the `@tanstack/react-query` major version (these rules are v5), whether `@tanstack/react-query-devtools` and `@tanstack/eslint-plugin-query` are installed, and where the project creates its `QueryClient` and keeps its query options. Extend those; don't add a second provider.- In a Next.js App Router project, read `node_modules/next/dist/docs/01-app/02-guides/client-side-data-fetching/` (`index.md` and `tanstack-query.md`): providers, hydration and Cache Components for the installed Next.js.## When to use it- Use it for server data that Client Components share, refetch, paginate, poll, update optimistically or load after an interaction (search as you type, infinite lists, live dashboards).…

Or let the shadcn CLI write this file: sign in (free) for your command.

Cursor

A project rule with front matter

  1. Save it as .cursor/rules/tanstack-query.mdc, front matter included, or run the install command.
  2. Cursor attaches it when you work on matching files, or when the task fits its description.
  3. Check it under Cursor Settings, Rules, then commit the folder for your team.

.cursor/rules/tanstack-query.mdc

---description: "TanStack Query v5 in React: query options and keys, caching, pagination, infinite queries, mutations, optimistic updates and SSR hydration."alwaysApply: false---# TanStack QueryYou are working in a React 19 project with TypeScript and TanStack Query v5 (`@tanstack/react-query`). Write code for the version in `package.json`: v5 renamed or removed options that most older examples still use.## Before you write code- Check the `@tanstack/react-query` major version (these rules are v5), whether `@tanstack/react-query-devtools` and `@tanstack/eslint-plugin-query` are installed, and where the project creates its `QueryClient` and keeps its query options. Extend those; don't add a second provider.- In a Next.js App Router project, read `node_modules/next/dist/docs/01-app/02-guides/client-side-data-fetching/` (`index.md` and `tanstack-query.md`): providers, hydration and Cache Components for the installed Next.js.## When to use it- Use it for server data that Client Components share, refetch, paginate, poll, update optimistically or load after an interaction (search as you type, infinite lists, live dashboards).…

Or let the shadcn CLI write this file: sign in (free) for your command.

Windsurf

A workspace rule for Cascade

  1. Save it as .windsurf/rules/tanstack-query.md, or run the install command.
  2. trigger: model_decision lets Cascade pull it in when the task fits its description.
  3. Windsurf reads up to 12,000 characters per rule; this one is 10,399.

.windsurf/rules/tanstack-query.md

---trigger: model_decisiondescription: "TanStack Query v5 in React: query options and keys, caching, pagination, infinite queries, mutations, optimistic updates and SSR hydration."---# TanStack QueryYou are working in a React 19 project with TypeScript and TanStack Query v5 (`@tanstack/react-query`). Write code for the version in `package.json`: v5 renamed or removed options that most older examples still use.## Before you write code- Check the `@tanstack/react-query` major version (these rules are v5), whether `@tanstack/react-query-devtools` and `@tanstack/eslint-plugin-query` are installed, and where the project creates its `QueryClient` and keeps its query options. Extend those; don't add a second provider.- In a Next.js App Router project, read `node_modules/next/dist/docs/01-app/02-guides/client-side-data-fetching/` (`index.md` and `tanstack-query.md`): providers, hydration and Cache Components for the installed Next.js.## When to use it- Use it for server data that Client Components share, refetch, paginate, poll, update optimistically or load after an interaction (search as you type, infinite lists, live dashboards).…

Or let the shadcn CLI write this file: sign in (free) for your command.

GitHub Copilot

Instructions for the repository

  1. Save it as .github/instructions/tanstack-query.instructions.md, or run the install command.
  2. applyTo: "**" applies it to every request in this repository.
  3. Or paste it into .github/copilot-instructions.md, which Copilot reads for every request.

.github/instructions/tanstack-query.instructions.md

---description: "TanStack Query v5 in React: query options and keys, caching, pagination, infinite queries, mutations, optimistic updates and SSR hydration."applyTo: "**"---# TanStack QueryYou are working in a React 19 project with TypeScript and TanStack Query v5 (`@tanstack/react-query`). Write code for the version in `package.json`: v5 renamed or removed options that most older examples still use.## Before you write code- Check the `@tanstack/react-query` major version (these rules are v5), whether `@tanstack/react-query-devtools` and `@tanstack/eslint-plugin-query` are installed, and where the project creates its `QueryClient` and keeps its query options. Extend those; don't add a second provider.- In a Next.js App Router project, read `node_modules/next/dist/docs/01-app/02-guides/client-side-data-fetching/` (`index.md` and `tanstack-query.md`): providers, hydration and Cache Components for the installed Next.js.## When to use it- Use it for server data that Client Components share, refetch, paginate, poll, update optimistically or load after an interaction (search as you type, infinite lists, live dashboards).…

Or let the shadcn CLI write this file: sign in (free) for your command.

Codex & AGENTS.md

The file most agents read

  1. Paste it into AGENTS.md at your project's root, under its own heading.
  2. Codex reads AGENTS.md before it starts work, and so do Cursor, Zed, Jules, Amp and Copilot's coding agent.
  3. A folder can have its own AGENTS.md: the one closest to the file being edited wins.

AGENTS.md

## TanStack QueryYou are working in a React 19 project with TypeScript and TanStack Query v5 (`@tanstack/react-query`). Write code for the version in `package.json`: v5 renamed or removed options that most older examples still use.### Before you write code- Check the `@tanstack/react-query` major version (these rules are v5), whether `@tanstack/react-query-devtools` and `@tanstack/eslint-plugin-query` are installed, and where the project creates its `QueryClient` and keeps its query options. Extend those; don't add a second provider.- In a Next.js App Router project, read `node_modules/next/dist/docs/01-app/02-guides/client-side-data-fetching/` (`index.md` and `tanstack-query.md`): providers, hydration and Cache Components for the installed Next.js.### When to use it- Use it for server data that Client Components share, refetch, paginate, poll, update optimistically or load after an interaction (search as you type, infinite lists, live dashboards).…

AGENTS.md is shared with your own instructions, so paste the rules in: the copy sets their headings a level down.

Rules teach it. The MCP equips it. With Pro, your agent installs every component, block and icon it needs, from a prompt.

Get Pro

Pricing

Give your agent the parts, with Pro

The prompts are free. Every Pro plan adds the MCP server, so your agent installs the components, blocks and icons it writes code around.

4.9/5 out of 5 · 150+ reviewsfrom 150+ founders and developers.

Billed once a year in USD. Cancel anytime.

Individual

For personal projects & open source

$32instead of $49/mo

$389 billed yearly

  • How many people can use the license.Seats: 1 seat
  • Client work, SaaS products and anything you charge for.Commercial use: Personal & open source only
  • Page sections in 56 categories, from heroes and pricing tables to dashboards and settings, on every framework and UI library, in your theme.Blocks: All 6,181 blocks
  • Complete Next.js sites for startups, SaaS, agencies and portfolios, every page designed, ready to ship.Templates: All 14 templates
  • Your AI agent searches, previews and installs any component, block, icon or DESIGN.md from a prompt: 22 tools in Claude Code, Cursor, VS Code and 35 more clients.MCP server: 38 AI clients
  • Buttons with motion and states of their own, from an arrow that slides in to a burst of confetti.Animated buttons: All 35 buttons
  • Typewriters, scrambles, counters and more, for headlines, labels and numbers.Text effects: All 97 effects
  • Animated backgrounds for heroes, sections and cards, from a soft aurora to a flock that flees the cursor. Most are pure CSS.Backgrounds: All 100 backgrounds
  • WebGPU shaders, from a slow aurora to a black hole, each drawing only while it's on screen.Shaders: All 100 shaders
  • One design and one theme on all four: the components, blocks and effects are built for each framework.Frameworks: React, Vue, Svelte, Solid
  • React on Base UI, Radix UI, React Aria or Ark UI: install the version for the library your project already uses. Vue, Svelte and Solid run on Ark UI.UI libraries: Base UI, Radix, Aria, Ark
  • From a button to a date picker, a color picker or a guided tour: accessible, typed in TypeScript, each with live demos.Components: All 174 components
  • Real-world patterns built from the components, in 53 galleries: forms, tables, menus, dialogs and more.Examples: All 1,103 examples
  • Area, bar, line, pie, radar and radial charts on TanStack Charts, on every framework. React also gets the Recharts originals.Charts: All 70 charts
  • Debounce, local storage, media queries and more: each hook is one typed file you own.React hooks: All 35 hooks
  • From 222 libraries, Lucide, Heroicons, Tabler and Material Symbols among them, as SVG or a React component in one command.Icons: 294k+ icons
  • Stripe, Linear, Apple and 497 more: a brand's colors, typography and components in one file your AI agent builds from.DESIGN.md files: All 500 brands
  • Restyle every component and block at once: 8 styles, 7 grays, 18 colors and 5 radii, shared as a shadcn preset code.Theme builder: 8 styles × 18 colors
  • Priority requests are answered first.Support: Standard

Team

Most popular

For freelancers, agencies & startups

$66instead of $99/mo

$789 billed yearly · ≈ $13/dev/mo

Everything in Individual, plus

  • How many people can use the license.Seats: Up to 5 seats
  • Client work, SaaS products and anything you charge for.Commercial use: Included
  • Priority requests are answered first.Support: Priority

Organization

For companies with larger teams

$132instead of $199/mo

$1,589 billed yearly

Everything in Team, plus

  • How many people can use the license.Seats: Unlimited seats
  • Secure checkout via Stripe
  • Code lives in your repo
  • Keep what you've shipped if you cancel
  • License terms

TanStack Query prompt FAQ

What the TanStack Query prompt covers and how to use it in your agent.

What is the difference between staleTime and gcTime in the TanStack Query prompt?

staleTime decides how long data counts as fresh, so mounts, window focus and reconnects don't refetch it; it defaults to 0. gcTime, called cacheTime before v5, decides how long unused data stays in memory, five minutes by default. The prompt has the agent set a staleTime default on the QueryClient and treat gcTime as memory only.

Why does the TanStack Query prompt use queryOptions for every query?

One queryOptions object holds the key, the fetcher and the options, so useQuery, useSuspenseQuery, prefetchQuery and getQueryData share one identity and the cache stays typed. It also stops server prefetches and client reads from drifting onto different keys, the usual reason hydrated data is fetched twice.

When does the TanStack Query prompt pick useSuspenseQuery over useQuery?

useSuspenseQuery when a Suspense boundary owns the loading state and an error boundary owns errors; its data is always defined. useQuery when the component renders its own pending and error states, or needs enabled or placeholderData, which suspense queries don't take.

How does the TanStack Query prompt handle pagination and infinite scroll?

Paged lists keep the previous page on screen with placeholderData set to keepPreviousData and use isPlaceholderData to disable the next button. Infinite lists use useInfiniteQuery with the required initialPageParam and a getNextPageParam that returns undefined at the end, and fetch the next page only when one exists and none is loading.

4.9 out of 5 from 150+ developers

Rules teach it. The MCP equips it.

Every component, block and icon, installed by your AI agent on your stack. The prompts stay free.

Get Pro
  • Cancel anytime
  • Keep what you've shipped