このページは英語でのみご覧いただけます。
TanStack Query
Pathruleルール2件 • メモリ3件 • スキル1件
A guardrail bundle for TanStack Query v5 in React apps. It enforces structured query key factories, deliberate staleTime, and mutation-driven invalidation so server state stays consistent. The rules keep data fetching out of effects and push every query through reusable queryOptions.
推奨されるパスの配置
Pathruleは各要素を対応するパスに配置するため、アシスタントはそれが属する場所でだけ参照します。これはインポート時に適用されるスコープで、ワークスペースで調整できます。
/ ワークスペースのルート
tanstack-query-review
src/
Never fetch server state in useEffect
Invalidate related queries after every mutation
TanStack Query v5 defaults you must override
api/
Query key factories are the cache contract
app/
Next.js App Router QueryClient setup and prefetch
ルール
2Never fetch server state in useEffect/src高強制Use useQuery or useSuspenseQuery for any server data, never useEffect plus useState.
| 1 | Server state belongs to TanStack Query, not to component effects. Fetching in `useEffect` and storing the result in `useState` reintroduces every problem the library exists to solve: no caching, no request dedupe, manual loading and error flags, and race conditions when the user navigates faster than the request resolves. |
| 2 | |
| 3 | - Replace any `useEffect` that calls `fetch` or an API client with `useQuery` or `useSuspenseQuery`. |
| 4 | - Read status from `query.status`, `isPending`, and `isError`, not from hand-rolled `useState` flags. (`isLoading` in v5 means `isPending && isFetching`; the initial-load flag is `isPending`.) |
| 5 | - Reference `query.data` directly in render. Do not copy it into `useState` or a global store, or background refetches will no longer reflect in the UI. |
| 6 | - For dependent queries, gate with the `enabled` option or compose `useSuspenseQuery` components; never gate a fetch inside an effect. |
| 7 | - Reserve `useEffect` for true side effects such as subscriptions and DOM work. |
メモリ
3Query key factories are the cache contract/src/apiCentralize hierarchical query keys per entity so invalidation is predictable.
| 1 | Query keys are how TanStack Query identifies, dedupes, and invalidates cache entries, so they live in one factory per entity instead of as scattered inline arrays. |
| 2 | |
| 3 | - Shape each factory hierarchically so a single call can clear a whole entity: |
| 4 | |
| 5 | ```ts |
| 6 | export const todoKeys = { |
| 7 | all: ['todos'] as const, |
| 8 | lists: () => [...todoKeys.all, 'list'] as const, |
| 9 | list: (filters: TodoFilters) => [...todoKeys.lists(), filters] as const, |
| 10 | details: () => [...todoKeys.all, 'detail'] as const, |
| 11 | detail: (id: string) => [...todoKeys.details(), id] as const, |
| 12 | } |
| 13 | ``` |
| 14 | |
| 15 | - Include EVERY input the query depends on (filters, ids, pagination) in the key. The key is the dependency array: changing it is how a new fetch is triggered, and omitting an input serves stale data for the wrong arguments. |
| 16 | - Pair each factory with `queryOptions()` so the same key, `queryFn`, and `staleTime` are reused verbatim across `useQuery`, `useSuspenseQuery`, `prefetchQuery`, and `setQueryData`. This also makes `queryClient.getQueryData(todoKeys.detail(id))` fully typed. |
| 17 | - Keys must be JSON-serializable; objects are compared by deep structure, not reference, but key order within an array matters (`['todos', 'list']` differs from `['list', 'todos']`). |
TanStack Query v5 defaults you must override/srcKnow the v5 freshness defaults before tuning refetch and cache behavior.
| 1 | TanStack Query v5 (`@tanstack/react-query` 5.x) ships aggressive freshness defaults that surprise teams expecting cache-first behavior. Set them deliberately per query rather than fighting symptoms. |
| 2 | |
| 3 | - `staleTime` defaults to `0`, so data is stale the instant it arrives and refetches on mount, window focus, and reconnect. For data that does not change every second, set a realistic value such as `staleTime: 5 * 60 * 1000`. |
| 4 | - `gcTime` (renamed from `cacheTime` in v5) defaults to 5 minutes and controls when INACTIVE cache entries are garbage collected. It is not staleness; a fresh-but-inactive query can still be collected. |
| 5 | - `retry` defaults to `3` with exponential backoff; turn it down or off for mutations and for endpoints where retrying is pointless (4xx). |
| 6 | - `staleTime: Infinity` keeps data fresh forever but still honors `invalidateQueries`; use it for data that only changes through your own mutations. `staleTime: 'static'` (v5) never refetches at all, even on manual invalidation, for truly immutable data like build-time feature flags. |
| 7 | - v5 removed `useQuery({ suspense: true })`. Use the dedicated `useSuspenseQuery`; it has no `enabled` option because loading is handled by Suspense and errors by an Error Boundary. `keepPreviousData` is also gone, replaced by `placeholderData: keepPreviousData` (imported from the package). |
Next.js App Router QueryClient setup and prefetch/appOne QueryClient per request on the server, a browser singleton on the client, then hydrate.
| 1 | The most damaging real-world TanStack Query bug in Next.js is a module-level QueryClient: on the server it is shared across all requests, so one user's cached data leaks to the next. Use a request-scoped factory. |
| 2 | |
| 3 | - Create the client through a helper that returns a fresh instance on the server and a memoized singleton in the browser (the browser branch prevents a new client when React suspends mid-render): |
| 4 | |
| 5 | ```ts |
| 6 | function makeQueryClient() { |
| 7 | return new QueryClient({ |
| 8 | defaultOptions: { queries: { staleTime: 60 * 1000 } }, |
| 9 | }) |
| 10 | } |
| 11 | let browserClient: QueryClient | undefined |
| 12 | export function getQueryClient() { |
| 13 | if (typeof window === 'undefined') return makeQueryClient() |
| 14 | return (browserClient ??= makeQueryClient()) |
| 15 | } |
| 16 | ``` |
| 17 | |
| 18 | - Set a default `staleTime` above `0` (60s is the documented baseline). Without it the client immediately refetches everything you just rendered on the server, defeating SSR. |
| 19 | - In a Server Component, prefetch with `getQueryClient()` + `await queryClient.prefetchQuery(todoQueryOptions(id))`, then render `<HydrationBoundary state={dehydrate(queryClient)}>`. Child Client Components call `useQuery(todoQueryOptions(id))` with the same `queryOptions` and read from the hydrated cache. |
| 20 | - For streaming, do not `await` the prefetch and enable pending-query dehydration (v5.40+): set `dehydrate.shouldDehydrateQuery` to `(q) => defaultShouldDehydrateQuery(q) || q.state.status === 'pending'`. Keep `'use client'` and the `QueryClientProvider` in a single shared providers component. |
スキル
1tanstack-query-review/rootPre-merge checklist for TanStack Query v5 usage.
| 1 | --- |
| 2 | name: tanstack-query-review |
| 3 | description: Review checklist for TanStack Query v5 code. Use when adding or changing useQuery, useSuspenseQuery, useMutation, query keys, queryOptions, QueryClient config, or Next.js SSR hydration in a React or Next.js codebase. |
| 4 | --- |
| 5 | |
| 6 | # TanStack Query review |
| 7 | |
| 8 | - [ ] No server data is fetched in `useEffect` plus `useState`; every read uses `useQuery` or `useSuspenseQuery` |
| 9 | - [ ] `query.data` is read directly in render, never copied into `useState` or a global store |
| 10 | - [ ] Query keys come from a centralized factory and include every input the query depends on (filters, ids, pagination) |
| 11 | - [ ] Shared queries are defined with `queryOptions()` and reused across hooks, `prefetchQuery`, and `setQueryData` |
| 12 | - [ ] `staleTime` is set deliberately rather than left at the `0ms` default; `gcTime`, `retry`, and `staleTime: Infinity` vs `'static'` are used with intent |
| 13 | - [ ] Every `useMutation` invalidates or updates the queries its write affects in `onSuccess` or `onSettled`, using key-factory references |
| 14 | - [ ] Optimistic updates cancel in-flight queries, snapshot previous data, roll back in `onError`, and reconcile in `onSettled` |
| 15 | - [ ] Loading and error states read from `isPending` and `isError` (or Suspense + Error Boundary), not hand-rolled flags |
| 16 | - [ ] In Next.js, the server uses a request-scoped QueryClient (no module-level instance) with a default `staleTime > 0`, and prefetched data is passed through `HydrationBoundary` + `dehydrate` |
| 17 | - [ ] No deprecated v5 APIs: no `useQuery({ suspense })`, no `cacheTime`, no `keepPreviousData` (use `placeholderData: keepPreviousData`) |
このパターンを使う理由
Server state drifts out of sync because queries use ad-hoc keys, fetch inside effects, and never invalidate after mutations.
対象:React and Next.js teams using TanStack Query v5 for server state
アシスタントが次のことをしないようにします。
- Fetching data inside useEffect and storing it in component state instead of using useQuery
- Ad-hoc string query keys that make invalidation unpredictable and silently serve stale data
- Mutations that update the server but never invalidate or update the related query cache
- ライセンス
- Apache-2.0
- バージョン
- 1.0.1
- 更新日
- 2026-08-25