---
title: "TanStack Queryとは？React Queryとの違い・v5の使い方と既定値の落とし穴"
url: "https://www.issoh.co.jp/tech/details/10307/"
published: 2025-12-17
updated: 2026-10-29
categories: ["React"]
publisher: "株式会社一創"
---

# TanStack Queryとは？React Queryとの違い・v5の使い方と既定値の落とし穴

TanStack Query（読み方は「タンスタッククエリ」）は、APIから取ってきたデータのキャッシュ・再取得・更新後の整合をまとめて引き受けるライブラリです。もとは React 専用の **React Query** で、v4 から TanStack の名前に統合され、React・Vue・Solid・Svelte・Angular などで同じ仕組みを使えるようになりました。React 版のパッケージは `@tanstack/react-query` で、2026年10月17日公開の 5.104.4 が最新です。この記事では React Query との関係、useQuery・useMutation の書き方、v4 から変わった API、そして **retry や gcTime の既定値がサーバー側で変わる**といった、ドキュメントを通読しないと気づきにくい挙動を 5.104.4 で実測した結果まで扱います。

## まとめ：TanStack Queryの要点

- **正体**：React Query の後継。サーバーのデータを「キーごとのキャッシュ」として持ち、古くなったら裏で取り直す。取得処理そのもの（fetch や axios）は自分で書く。
- **現在の版**：v5 系（React 版 5.104.4・2026-10-17）。React 18 以上が必要で、TypeScript は直近2年に出た版（現在は5.7以上）が公式の対応範囲。旧 `react-query` パッケージは 3.39.3 で止まっている。
- **v4からの主な変更**：引数はオブジェクト1つに統一、`cacheTime`→`gcTime`、`status: "loading"`→`"pending"`、useQuery の `onSuccess` などは廃止、`Hydrate`→`HydrationBoundary`。
- **既定値**：staleTime は0、失敗時は3回リトライ（1秒・2秒・4秒の間隔）、未使用のキャッシュは5分で破棄。ただしサーバーと判定される環境ではリトライ0回・gcTime は Infinity になる。
- **使いどころ**：一覧・詳細・検索結果など「サーバーのデータを表示し、更新後に揃えたい」画面。フォームの入力値やモーダルの開閉は対象外。

## TanStack Queryの役割とReact Queryとの関係

TanStack Query が扱うのはサーバー状態、つまり自分のアプリが持ち主ではなく、別の誰かがいつ書き換えたか分からないデータです。`useState` と `useEffect` で取得すると、読み込み中の判定、エラー表示、同じ API への重複リクエストの抑制、画面に戻ったときの取り直しを毎回手で書くことになります。TanStack Query はこれを引き受け、コンポーネント側は「どのキーで、どの関数で取るか」だけを宣言します。公式の [Overview](https://tanstack.com/query/latest/docs/framework/react/overview) も、サーバー状態はクライアント状態と性質が違うので専用の道具が要る、という動機から説明を始めています。

### React Queryからの改名と読み方

React Query と TanStack Query は同じライブラリです。v3 までは `react-query` というパッケージ名で、v4 から `@tanstack/react-query` に移りました。npm の `react-query` は 2023年1月の 3.39.3 で更新が止まっていますが、[npm のダウンロード統計](https://api.npmjs.org/downloads/point/last-week/react-query)では2026年10月10日〜16日の1週間でも約94万回ダウンロードされており（`@tanstack/react-query` は約4,063万回）、古いパッケージのまま動いているプロジェクトはまだ残っています。「タンスタック」は作者 Tanner Linsley 氏の名前に由来するブランドで、同じ傘の下に [TanStack Routerとは？型安全ルーティングの特徴・導入手順とReact Routerとの違い](/tech/details/9913/) で扱う Router や、Table・Form などが並びます。

### 対応フレームワークとパッケージの版

共通の処理は `@tanstack/query-core` にあり、各フレームワーク向けのパッケージはその上に薄く載っています。版番号はアダプタごとに揃っていない点に注意が要ります（2026年10月18日に npm レジストリの dist-tags と peerDependencies で確認）。

| 対象      | パッケージ                                | 最新版     | 備考              |
| ------- | ------------------------------------ | ------- | --------------- |
| React   | @tanstack/react-query                | 5.104.4 | React 18 / 19   |
| Vue     | @tanstack/vue-query                  | 5.104.4 | Vue 2.6 / 3.3以上 |
| Solid   | @tanstack/solid-query                | 5.104.4 | 6.0.0はRC段階      |
| Svelte  | @tanstack/svelte-query               | 6.3.4   | Svelte 5.25以上   |
| Angular | @tanstack/angular-query-experimental | 5.104.4 | 実験版。後継はRC段階     |
| Preact  | @tanstack/preact-query               | 5.104.4 | 2026年2月に初版      |
| Lit     | @tanstack/lit-query                  | 0.2.29  | 実験版（2026年5月初版）  |

Angular 版は公式ドキュメントで「マイナー版やパッチ版でも破壊的変更が入る」実験段階とされており、Lit 版も同じく実験段階です。Angular では2026年10月17日に後継の `@tanstack/angular-query` 5.0.0-rc.5（リリース候補）が公開され、リリースノートは Angular 20.1 以上向けの「stable」なアダプタとして、experimental からの移行用 schematic を同梱すると説明しています。React Native では同じ `@tanstack/react-query` がそのまま動きますが、オンライン復帰時の再取得は `onlineManager` に NetInfo をつなぐ設定が別途必要です。アプリ側の設計から相談したい場合は [Flutter / React Native開発](/service/apps/cross%5Fplatform/) の支援内容も参考になります。

### 向く画面・向かない画面

向くのは、一覧・詳細・検索結果・ページ送りなど、サーバーのデータを読んで、作成や更新のあとに最新へ揃えたい画面です。入力中のフォーム値、モーダルの開閉、タブの選択といったクライアント状態は対象外で、`useState` や Zustand などで持ちます。フォーム値までキャッシュに載せると、キーの設計と無効化のタイミングが絡んでかえって複雑になります。フォームは [React Hook Formとは？使い方・バリデーション・v7の書き方を実例で解説](/tech/details/2898/) のような専用ライブラリに任せるのが素直です。

## v4からv5への移行で変わったAPI

v5.0.0 は2023年10月17日に公開され、この記事では v5 の API を扱います。v4 系も 4.44.0（2026年4月1日）まで更新が続き、v5 の `usePrefetchQuery`（4.43.0）や `mutationOptions`（4.44.0）がバックポートされていますが、`gcTime` の改名や `HydrationBoundary` など v5 の設計変更は入りません。[公式の移行ガイド](https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5)が挙げる変更のうち、古い記事のコードが動かなくなる原因になりやすいものを順に見ます。

### 引数のオブジェクト統一とuseQueryのコールバック廃止

v4 では `useQuery(key, fn, options)` のような複数の呼び方が許されていましたが、v5 では引数がオブジェクト1つだけになりました。あわせて useQuery の `onSuccess`・`onError`・`onSettled` が削除されています（useMutation の同名コールバックは残っています）。

```
// v4の書き方（v5では型エラー）
const { isLoading } = useQuery(["todos"], fetchTodos, { cacheTime: 300000 });

// v5
const { isPending } = useQuery({
  queryKey: ["todos"],
  queryFn: fetchTodos,
  gcTime: 300000, // cacheTime から改名
});
```

useQuery のコールバック削除には批判もあり、Reddit には「v5 はダウングレードに感じる」という題のスレッドがあります。メンテナーの TkDodo 氏は v5 公開前の2023年4月16日に [Breaking React Query’s API on purpose](https://tkdodo.eu/blog/breaking-react-querys-api-on-purpose) で理由を説明しており、要点は2つです。コールバックは同じクエリを使うコンポーネントの数だけ呼ばれるので、トースト通知を出すと同じ文言が複数回出る。もう一つは、キャッシュから値が返って通信が起きなかったときは `onSuccess` が呼ばれないため、「データが来たら state に写す」使い方は取りこぼす、という点です。代わりの書き方は用途で分かれます。

- **エラー通知を1回だけ出したい**：`new QueryClient({ queryCache: new QueryCache({ onError }) })` のグローバルなコールバックに置く。クエリ単位で文言を変えたいときは `meta` に入れて、そこから読む。
- **取得結果から別の値を作りたい**：state に写さず、`data` から毎回計算するか `select` で変換する。
- **どうしても外部の state と同期したい**：`useEffect` で `data` を監視する。`data` は、既定の構造共有が有効でJSON互換の値を返す場合、中身が同じなら参照が維持されるため、同内容の再取得によるEffectの再実行を抑えられます。

### cacheTimeからgcTime、loadingからpendingへの改名

`cacheTime` は `gcTime`（garbage collection time）に改名されました。意味は「キャッシュが有効な時間」ではなく、そのクエリを使うコンポーネントが1つも無くなってから、キャッシュを捨てるまでの猶予です。状態も `status: "loading"` が `"pending"` に、フラグが `isPending` に変わりました。v5 にも `isLoading` はありますが、中身は `isPending && isFetching`（キャッシュが無く、かつ通信中）を表す派生フラグで、v4 の `isInitialLoading` にあたります。両者の差が出る場面は後の実測章で確認します。

### keepPreviousDataとHydrateの置き換え

ページ送りの間に前のページを表示し続ける `keepPreviousData: true` は、`placeholderData` に同名の関数を渡す形になりました。SSR でキャッシュを受け渡すコンポーネントは `Hydrate` から `HydrationBoundary` に改名されています。

```
import { keepPreviousData, useQuery } from "@tanstack/react-query";

const { data, isPlaceholderData } = useQuery({
  queryKey: ["projects", page],
  queryFn: () => fetchProjects(page),
  placeholderData: keepPreviousData, // v4の keepPreviousData: true
});
```

オーバーロードの書き換えは件数が多くなるので、公式の codemod（jscodeshift）で機械的に直せます。TypeScript では parser に `tsx` を指定しないと適用されない、と移行ガイドに明記されています。v5 では TypeScript のエラー型の既定が `unknown` から `Error` に変わった点も、型チェックの差分として出てきます。

```
npx jscodeshift@latest ./src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/build/codemods/v5/remove-overloads/remove-overloads.cjs
```

## インストールとQueryClientの設定

本体と、キャッシュの中身を画面で確認できる Devtools を入れます。Devtools は `process.env.NODE_ENV === 'development'` のときだけバンドルに含まれる作りなので、本番ビルドから外す設定は要りません。

```
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
```

`QueryClient` がキャッシュの本体で、`QueryClientProvider` でアプリ全体に配ります。既定値は `defaultOptions` でまとめて変えられます。staleTime を明示しておくと、既定の0による取り直しの多さを抑えられます。

```
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: { staleTime: 60 * 1000 },
  },
});

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Todos />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}
```

## useQueryによるデータ取得とエラー処理

`useQuery` には、キャッシュの住所になる `queryKey` と、Promise を返す `queryFn` を渡します。`fetch` は 404 や 500 でも例外を投げないため、`res.ok` を見て自分で投げないと、エラー画面ではなく「エラー内容を data として表示」する状態になります。公式の [Query Functions](https://tanstack.com/query/latest/docs/framework/react/guides/query-functions) もこの点を注意しています。

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

type Todo = { id: number; title: string };

async function fetchTodos(): Promise<Todo[]> {
  const res = await fetch("/api/todos");
  if (!res.ok) throw new Error(`HTTP ${res.status}`); // fetchは自分で投げる
  return res.json();
}

export function Todos() {
  const { data, isPending, isError, error } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  });

  if (isPending) return <p>読み込み中...</p>;
  if (isError) return <p>エラー: {error.message}</p>;
  return <ul>{data.map((t) => <li key={t.id}>{t.title}</li>)}</ul>;
}
```

### queryKeyの設計と一致の規則

queryKey は配列で、先頭から階層として扱われます。`["todos"]` を無効化すると `["todos", 1]` や `["todos", { status: "done" }]` もまとめて対象になるので、「リソース名 → ID → 絞り込み条件」の順に並べておくと更新後の取り直しが1行で済みます。配列内のオブジェクトはキーの順序を問わず同じものとして扱われますが、配列の要素の順序は区別されます（実測は次章）。キーとクエリ関数の組を使い回すなら、`queryOptions` でまとめておくと `getQueryData` の戻り値にも型が付きます。

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

export const todoQuery = (id: number) =>
  queryOptions({
    queryKey: ["todos", id],
    queryFn: () => fetch(`/api/todos/${id}`).then((r) => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json() as Promise<Todo>;
    }),
  });

// useQuery(todoQuery(1)) でも queryClient.getQueryData(todoQuery(1).queryKey) でも同じ型
```

### よく使うオプション：enabled・select・retry・refetchInterval

| オプション                | 役割             | 既定値       |
| -------------------- | -------------- | --------- |
| enabled              | false の間は取得しない | true      |
| select               | data を変換して返す   | なし        |
| retry                | 失敗時の再試行回数      | 3（サーバーは0） |
| refetchInterval      | 一定間隔で取り直す      | false     |
| refetchOnWindowFocus | タブに戻ったとき取り直す   | true      |

`enabled` はログイン後にしか呼べない API や、前のクエリの結果を待つ依存クエリに使います。`select` は一覧の一部だけを使うコンポーネントで効き、変換後のdataだけを参照する場合、その値が変わらなければデータ更新による再描画を抑えられます。isFetchingなど、ほかに参照しているプロパティが変われば再描画されます。

```
const { data: userName } = useQuery({
  queryKey: ["user", userId],
  queryFn: () => fetchUser(userId!),
  enabled: userId !== undefined, // userIdが決まるまで取得しない
  select: (user) => user.name,
});
```

## 5.104.4の既定値とクライアント・サーバー別の実測結果

公式の [Important Defaults](https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults) は既定値を列挙していますが、組み合わせたときの動きまでは書いていません。そこで `@tanstack/query-core` 5.104.4 を Node.js v26.5.0 の QueryObserver で直接動かして確かめました。React 版も同じ query-core の上で動きます。クライアントの状態は `environmentManager.setIsServer(() => false)` で再現しています。

| 試したこと                               | クライアント                         | サーバー判定   |
| ----------------------------------- | ------------------------------ | -------- |
| 同じキーを3か所で同時に購読                      | queryFn 1回                     | 1回       |
| 取得後に2つ目を購読（staleTime 0）             | 計2回                            | 計2回      |
| 同（staleTime 60秒）                    | 計1回                            | 計1回      |
| 失敗し続ける queryFn                      | 4回（0・1.0・3.0・7.0秒）             | 1回       |
| gcTime の既定値                         | 300000（5分）                     | Infinity |
| enabled: false の初期状態                | isPending true／isLoading false | 同じ       |
| 同じ中身で再取得（構造共有あり）                    | data の参照は同一                    | 同一       |
| { status, page } と { page, status } | 同じキャッシュ                        | 同じ       |

### 同一キーの重複排除とstaleTimeによる再取得の差

同じキーを使うコンポーネントが一度に3つ表示されても、リクエストは1回です。ところが1つ目の取得が終わったあとに別のコンポーネントが同じキーで表示されると、staleTime が0なら取り直しが走ります。タブ切り替えで同じ一覧を何度も開く画面では、これが「キャッシュしているのに毎回通信している」ように見える正体です。staleTime を60秒にすると2回目は飛びませんでした。更新頻度が低いマスタデータは、Devtools で通信の回数を見ながら staleTime を伸ばすのが先です。

### サーバー判定時の既定値：retry 0回・gcTime Infinity

失敗し続ける queryFn は、クライアントでは初回を含めて4回呼ばれ、間隔は約1秒・2秒・4秒と倍々に伸びました。各試行が即座に失敗する今回の検証では、再試行の待ち時間が合計約7秒でした。実際には各リクエストの処理時間も加わるため、「エラーにすぐ切り替わらない」という問い合わせはまずこの既定値を疑います。一方、`window` が無い Node.js ではサーバー扱いになり、1回失敗した時点で error になりました。gcTime も Infinity です。公式の [Query Retries](https://tanstack.com/query/latest/docs/framework/react/guides/query-retries) にも、サーバーではレンダリングを速くするためにリトライの既定が0だと書かれています。

この差は2か所で効きます。1つは Jest や Vitest を jsdom なしの Node 環境で動かす場合で、テストではリトライしないのに本番ではする、というずれが起きます。テスト用の QueryClient で `retry: false` を明示すると、テスト環境によるリトライの差をなくせます。ただし、本番の既定値とは揃いません。本番と同じ再試行を検証するテストでは、双方に同じ retry を指定します。もう1つは SSR で、gcTime が Infinity のクライアントをモジュールの最上位に置いて全リクエストで共有すると、ユーザーごとのデータが破棄されずに溜まり、別のユーザーに見える恐れがあります。サーバーでは必ずリクエストごとに QueryClient を作ってください（次章のコード）。

### 未取得・enabled: falseのクエリにおけるisPendingとisLoadingの差

`enabled: false` のクエリは、データが無いので status は `pending`、通信していないので fetchStatus は `idle` になります。このため `isPending` でスピナーを出すと、条件が揃わない限り永遠に読み込み中の表示が続きます。依存クエリや「検索ボタンを押したら取得」の画面では、`isLoading`（pending かつ通信中）で分岐するのが正解です。

## staleTimeとgcTimeによる鮮度・保持期間の制御

staleTime はデータを「新しい」とみなす期間で、その間はマウントやフォーカスで取り直しません。gcTime は使われなくなったキャッシュを捨てるまでの猶予です。staleTime を過ぎてもキャッシュは残るので、画面に戻ると古いデータを即表示しつつ裏で取り直します。

| staleTime の値  | stale判定による自動再取得（他は既定設定） | invalidateQueries |
| ------------- | ----------------------- | ----------------- |
| 0（既定）         | マウント・フォーカス・再接続のたび       | 効く                |
| 60 \* 1000 など | 指定時間が過ぎてから              | 効く                |
| Infinity      | しない                     | 効く                |
| ‘static’      | しない                     | 効かない              |

`'static'` は v5 の途中で入った値で、Important Defaults では「アプリの実行中に変わらないデータ」向けとされ、起動時に取る機能フラグやログイン時に読む権限を例に挙げています。`refetchOnWindowFocus: "always"` のような強制の指定も止めるので、あとから手動で更新したくなる可能性があるデータには Infinity を使います。

## useMutationによるデータ更新とキャッシュの無効化

作成・更新・削除は `useMutation` で書き、成功したら関係するクエリを `invalidateQueries` で古い扱いにします。実測では、`["todos"]` を無効化すると表示中の `["todos", "active"]` はすぐ取り直され、表示されていない `["todos", "inactive"]` は取り直さずに「無効」の印だけが付きました。次に表示された時点で取りに行くので、画面に無いクエリのために通信が増えることはありません。

```
import { useMutation, useQueryClient } from "@tanstack/react-query";

export function AddTodo() {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: async (title: string) => {
      const res = await fetch("/api/todos", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ title }),
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    },
    // Promiseを返すと、再取得が終わるまで isPending のまま
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ["todos"] }),
  });
  return (
    <button disabled={mutation.isPending} onClick={() => mutation.mutate("買い物")}>
      追加
    </button>
  );
}
```

キャッシュを捨てる系の操作は3つあり、`invalidateQueries` は古い印を付けて取り直す、`resetQueries` は初期データに戻して取り直す、`removeQueries` はキャッシュから消すだけで取り直さない、という違いです。更新後の整合なら invalidateQueries、ログアウト時の後始末なら removeQueries か `queryClient.clear()` を使います。

### 楽観的更新とロールバック

通信の完了を待たずに画面を先に変え、失敗したら戻すのが楽観的更新です。現行のドキュメントでは、`onMutate` の第2引数に QueryClient を含む `context` が渡り、`onMutate` の戻り値は `onError` の第3引数（`onMutateResult`）で受け取ります。古い記事では第3引数を `context` と名付けていますが、同じ位置の値なので動作は変わりません。

```
useMutation({
  mutationFn: addTodo,
  onMutate: async (newTodo: Todo, context) => {
    await context.client.cancelQueries({ queryKey: ["todos"] });
    const previous = context.client.getQueryData<Todo[]>(["todos"]);
    context.client.setQueryData<Todo[]>(["todos"], (old) => old === undefined ? undefined : [...old, newTodo]);
    return { previous };
  },
  onError: (_err, _newTodo, onMutateResult, context) => {
    context.client.setQueryData(["todos"], onMutateResult?.previous); // 巻き戻し
  },
  onSettled: (_data, _err, _vars, _result, context) =>
    context.client.invalidateQueries({ queryKey: ["todos"] }),
});
```

一覧の1件を足すだけなら、キャッシュを触らずに `mutation.variables` を半透明で一覧の末尾に描く方法もあり、公式の Optimistic Updates ではこちらを「より簡単な方法」として先に紹介しています。複数の画面に同じデータが出るときだけキャッシュを直接書き換えます。

## Next.js App RouterのSSRとキャッシュの受け渡し

App Router では、サーバーコンポーネントで `prefetchQuery` し、`dehydrate` した結果を `HydrationBoundary` でクライアントへ渡します。公式の [Advanced Server Rendering](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr) は、サーバーでは毎回新しい QueryClient を作り、ブラウザでは1つを使い回す `getQueryClient` を用意する形を勧めています。サーバー側の判定には `environmentManager.isServer()` を使い、旧来の `isServer` 定数は非推奨になりました。

```
// app/get-query-client.ts
import { environmentManager, QueryClient } from "@tanstack/react-query";

function makeQueryClient() {
  // SSR直後にクライアントで即取り直さないよう staleTime を0より大きくする
  return new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000 } } });
}

let browserQueryClient: QueryClient | undefined;

export function getQueryClient() {
  if (environmentManager.isServer()) return makeQueryClient(); // リクエストごとに新規
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
  return browserQueryClient;
}

// app/todos/page.tsx（サーバーコンポーネント）
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";

export default async function Page() {
  const queryClient = getQueryClient();
  await queryClient.prefetchQuery({ queryKey: ["todos"], queryFn: fetchTodos });
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Todos />
    </HydrationBoundary>
  );
}
```

この例は構成の抜粋です。Provider は `'use client'` を指定した別ファイルで作り、`getQueryClient()` の戻り値を渡して layout から children を包みます。Todos もクライアントコンポーネントとして定義し、page.tsx では getQueryClient・Todos・サーバー用 fetchTodos をそれぞれ import してください。先の相対URLを使う fetchTodos はサーバーでは流用できないため、サーバー用には絶対URLのAPI呼び出し、またはデータ取得処理への直接アクセスを実装します。App Router の規約は [App Routerとは？Next.jsのファイル規約・動的ルーティング・Pages Routerとの違い](/tech/details/4026/)、Suspense と組み合わせる場合は [Async Reactとは？Transition・Suspense・useOptimisticで非同期UIを組む実装【React 19.3】](/tech/details/11005/) を参照してください。Next.js でなく TanStack 系で揃えるなら [TanStack Startとは？フルスタックReactフレームワークの特徴・使い方・デプロイ【2026年版】](/tech/details/10179/) も候補です。

## SWR・RTK Queryとの違いと選び方

3つともサーバー状態を扱いますが、得意な範囲が違います。公式の [Comparison](https://tanstack.com/query/latest/docs/framework/react/comparison) では、SWR は Selectors（`select` 相当）を持たない扱いで、キャッシュキーは TanStack Query が階層、SWR と RTK Query が単一キーとされています。バンドルの大きさは、対象APIをexportするエントリを esbuild 0.28.2 の –bundle –minify でビルドし、Reactをexternal指定したうえでgzip -9を適用して比較しました。実際のアプリに追加される容量は、既存依存関係や使用APIによって変わります。

| 観点          | TanStack Query | SWR       | RTK Query        |
| ----------- | -------------- | --------- | ---------------- |
| 実測サイズ（gzip） | 9,935バイト       | 6,413バイト  | 未計測              |
| キーの無効化      | 前方一致でまとめて      | キー単位・関数指定 | タグで指定            |
| select      | あり             | なし        | selectFromResult |
| 前提          | React以外も可      | React     | Redux            |

計測したのは `@tanstack/react-query` 5.104.4 の useQuery・QueryClient・QueryClientProvider と、swr 2.5.1 の useSWR・SWRConfig で、差は約3.5KBです。取得して表示するだけの画面が中心なら SWR で十分です。更新後に関連する一覧をまとめて取り直す、楽観的更新を多用する、といった書き込みの多いアプリでは、階層キーで無効化できる TanStack Query のほうがコードが短く済みます。Redux Toolkit で状態を持っているプロジェクトなら RTK Query が自然です。それぞれ [useSWRとは？使い方とfetcher・mutate・オプション既定値を解説【SWR 2.5対応】](/tech/details/4694/) と [Redux Toolkitとは？createSliceとRTK Queryの実装と採用判断【2026年8月版】](/tech/details/16013/) で詳しく扱っています。

## 脆弱性とセキュリティ対策

TanStack/query リポジトリのセキュリティアドバイザリは2026年10月18日時点で1件です。これとは別に、2026年5月に TanStack の名前空間を狙った供給網攻撃がありました。

### CVE-2024-24558：ストリーミングSSR用パッケージのXSS

対象は本体ではなく、Next.js のストリーミング SSR 用の実験パッケージ `@tanstack/react-query-next-experimental` です。影響範囲は 5.0.0 以上 5.18.0 未満、CVSS は 8.2（High）で、5.18.0 で修正されました（[GHSA-997g-27×8-43rf](https://github.com/advisories/GHSA-997g-27x8-43rf)）。このパッケージを使っていなければ影響はなく、使っている場合も現行の 5.104.4 に上げれば解消します。

### 2026年5月のnpm供給網攻撃とquery系パッケージ

2026年5月11日、npm 上で42の `@tanstack/*` パッケージに計84の悪意ある版が公開されました（CVE-2026-45321）。[公式のポストモーテム](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem)では `@tanstack/query*` を「confirmed-clean」としており、被害は Router・Start のリポジトリに限られます。それでも同じ名前空間を使う以上、`package-lock.json` をコミットし、CI では `npm ci` で入れて、身に覚えのない版が入り込まない状態にしておくのが最低限の備えです。

### ログアウト時のキャッシュ消去とユーザー間のデータ分離

キャッシュはブラウザのメモリにあり、ログアウトしても自動では消えません。共有端末でユーザーを切り替えると、前のユーザーの一覧が一瞬表示される事故が起きます。ログアウト処理で `queryClient.clear()` を呼んでください。5.104.4 では、clear() のあとに `useMutationState` と `useIsMutating` が正しく更新されない不具合も直っています。

## よくある質問

### React Queryはもう使えないのですか？

開発は TanStack Query として続いていますが、旧 `react-query` パッケージは 3.39.3 で止まっています。新規なら `@tanstack/react-query` の v5 を入れ、既存コードは codemod で引数を直してから gcTime などの改名を追います。

### TanStack Queryの読み方は？

「タンスタッククエリ」です。TanStack は作者 Tanner Linsley 氏の名前に由来します。表記は TanStack Query、React 版のパッケージ名は `@tanstack/react-query` です。

### isPendingとisLoadingはどう使い分けますか？

isPending はデータがまだ無い状態、isLoading はデータが無く、かつ通信中の状態です。通常の表示では isPending で構いませんが、`enabled: false` で止めているクエリは isPending が true のままになるので、読み込み表示には isLoading を使います。

### useQueryのonSuccessが使えません。代わりはありますか？

v5 で useQuery からは削除されました。エラー通知は QueryCache の onError、値の計算は select、外部 state との同期は useEffect で置き換えます。useMutation の onSuccess は使えます。

### v6はいつ出ますか？

2026年10月18日時点で、npm レジストリの版一覧を見る限り、React 版の v6 は alpha 版も含めて公開されていません。Svelte 版だけは Svelte 5 対応の 6.0.0 が2025年9月30日に出ており、Solid 版は 6.0.0 の RC 段階です。React で使う限りは v5 の書き方が当面の前提になります。

## 関連記事

- [TanStack Tableとは：v9の導入コードとヘッドレス設計・AG Gridとの選び分け](https://www.issoh.co.jp/tech/details/19067/)：TanStack Queryで取得したページ単位のデータを一覧表に流す実装例があります
- [TanStack DBとは？TanStack Queryとの違い・使い方とv0.9系の注意点](/tech/details/8738/)
- [TanStack Routerとは？型安全ルーティングの特徴・導入手順とReact Routerとの違い](/tech/details/9913/)
- [TanStack Startとは？フルスタックReactフレームワークの特徴・使い方・デプロイ【2026年版】](/tech/details/10179/)
- [useSWRとは？使い方とfetcher・mutate・オプション既定値を解説【SWR 2.5対応】](/tech/details/4694/)
- [Redux Toolkitとは？createSliceとRTK Queryの実装と採用判断【2026年8月版】](/tech/details/16013/)

---

出典: [TanStack Queryとは？React Queryとの違い・v5の使い方と既定値の落とし穴](<https://www.issoh.co.jp/tech/details/10307/>)（株式会社一創）
