TanStack Query v5を1ヶ月本番投入してわかったこと|SWRとの使い分けも【2026年版】
TanStack Query(React Query)v5を1ヶ月間本番環境で使って気づいたこと。キャッシュ挙動・SWRとの違い・v5の破壊的変更点をReactフリーランスが実体験で解説。
※当サイトはアフィリエイトプログラムに参加しています。記事内のリンクから商品を購入すると、当サイトに報酬が支払われることがあります。詳しくはプライバシーポリシーをご覧ください。
1ヶ月前、担当している案件のフロントエンドで「TanStack Query入れましょうか」と提案されたとき、僕は内心「うーん、SWRで十分じゃないの?」と思っていた。
正直に言うと。
でも、チームのテックリードが「v5から設計思想がかなり変わってる。GW前の落ち着いた時期に一度ちゃんと触ってみましょう」と言い、4月1日から使い始めることになった。
3週間と少し使った記録をそのまま書く。
(書き終えたら思ったより長くなった。すみません)
1週目:「え、onSuccessはどこ行った?」
v5の破壊的変更に最初の30分でやられた
セットアップ自体は5分で終わった。npm install @tanstack/react-query して、QueryClientProvider でラップして、useQuery を書く。ここはv4から変わっていない。
問題は、その後だ。
v4で書いていたコードをそのままv5に持ってきたら、VSCodeが赤線を引きまくった。
// v4では動いていたコード(v5ではエラー)
const { data } = useQuery('todos', fetchTodos, {
onSuccess: (data) => {
console.log('成功:', data);
},
onError: (error) => {
console.error('エラー:', error);
},
});
v5での主な変更点:
onSuccessとonErrorコールバックがuseQueryから削除された- 第1引数の文字列クエリキーが廃止(オブジェクト形式のみ)
- 代わりに副作用は
useEffectでdataやerrorを監視する形になった
// v5の正しい書き方
const { data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
useEffect(() => {
if (data) console.log('成功:', data);
}, [data]);
useEffect(() => {
if (error) console.error('エラー:', error);
}, [error]);
最初は「なんで?」と思ったけど、これは「副作用は副作用で管理する」という設計思想の変更だ。ライブラリのコールバックに副作用を混在させるのは、デバッグが難しくなるから——という理由は理にかなっている。
ただ、この変更に気づかず「なんで動かないんだ…」と悩んでいた時間が1.5時間くらいあった。公式のMigration Guideを最初に読んでおけばよかった、と今になって思う。
(ここ書くのに少し恥ずかしかった)
TypeScriptとの相性は抜群
TypeScriptを本格的に使っている人には朗報で、v5のTanStack Queryは型推論がかなり強化されている。
queryFn の戻り値から data の型が自動で推論されるし、error も Error | null と明示的に扱えるようになった。useQuery に型引数を手で書く場面がほとんどなくなった。
TypeScriptの基礎が固まっていると、このあたりの恩恵をかなり受けやすい。
2週目:ミューテーションとキャッシュ無効化で詰まる
useMutation + invalidateQueries の基本フロー
1週目でデータ取得(useQuery)の基本はつかめた。2週目からはデータの更新(useMutation)を実装した。
「TODOを追加したら一覧が自動更新される」というよくある要件。
const queryClient = useQueryClient();
const addTodoMutation = useMutation({
mutationFn: (newTodo: string) => postTodo(newTodo),
onSuccess: () => {
// キャッシュを無効化して再取得を促す
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
この部分は意外とすんなり動いた。「ミューテーションが成功したらキャッシュを無効化して再fetch」という流れが、コードを見ただけで理解できる。これはSWRより直感的だと感じた。
ハマりポイント:staleTime と gcTime の違い
正直ここが一番詰まった。
v5では cacheTime が gcTime(Garbage Collection Time)に改名されている。しかも staleTime との違いがわからなくて、2時間くらい混乱した。
整理するとこう:
| プロパティ | 意味 | デフォルト値 |
|---|---|---|
staleTime | データが「古い」とみなされるまでの時間。この間はrefetchしない | 0(即古くなる) |
gcTime | キャッシュがメモリから削除されるまでの時間 | 5分 |
staleTime: 0 がデフォルトなので、コンポーネントが再マウントされるたびに再fetchが走る。これがデフォルトの挙動として正しいのか、パフォーマンス的に問題ないのか、判断するのに時間がかかった。
フロントエンドのテストを書くときにも、このキャッシュの挙動をモックするのか実際のQueryClientを使うのかで選択肢が変わる。早めに理解しておいてよかった。
3週目:「あ、これめっちゃ便利じゃん」になった瞬間
Optimistic Updateがパターン化されている
「いいねボタン」の実装で、楽観的更新(Optimistic Update)を書く必要があった。
SWRでも実装できるが、TanStack Queryの方がパターンが明確に決まっていて書きやすかった。
const likeMutation = useMutation({
mutationFn: (postId: string) => likePost(postId),
onMutate: async (postId) => {
await queryClient.cancelQueries({ queryKey: ['posts'] });
const previousPosts = queryClient.getQueryData(['posts']);
queryClient.setQueryData(['posts'], (old: Post[]) =>
old.map(p => p.id === postId ? { ...p, liked: true } : p)
);
return { previousPosts };
},
onError: (err, postId, context) => {
queryClient.setQueryData(['posts'], context?.previousPosts);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
初めてこれを書いたとき、「ここまでパターン化されてると書きやすいな」と素直に思った。
SWRとの正直な比較
ちょっと話逸れるけど、3週目の終わりにSWRとの比較を改めてやった。
Zustand vs Reduxみたいな議論と同じで「どっちが絶対に正しい」という話ではなく、ユースケースによって向き不向きがある。
| 比較項目 | TanStack Query | SWR |
|---|---|---|
| バンドルサイズ | 約47KB | 約11KB |
| セットアップ | QueryClientProvider必要 | Provider不要(シンプル) |
| Optimistic Update | パターンが明確 | 自分で書く必要あり |
| DevTools | 専用DevToolsが強力 | SWR DevToolsはシンプル |
| 無限スクロール | useInfiniteQueryが強力 | useSWRInfiniteで対応 |
| ミューテーション | useMutationが充実 | useSWRMutationで対応 |
| 学習コスト | 高め | 低め |
ぶっちゃけ、シンプルなユースケースならSWRの方が学習コストが低い。 Next.jsのプロジェクトで「取得するだけ」「更新は単純なPOST」程度なら、SWRの方がコード量が少なくて済む。
TanStack Queryが本領発揮するのはこういうケースだ:
- Optimistic Updateが複数箇所に絡む
- キャッシュの細かい制御が必要
- 無限スクロールのような複雑なページネーション
- DevToolsでキャッシュの状態を可視化したい
4週目:本番環境に投入して気づいたこと
グローバルエラーハンドリングの設計
v4では onError を各 useQuery に書けていたが、v5では削除された。グローバルなエラーハンドリングは QueryClient の設定に書く。
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
throwOnError: (error) => {
// 500エラー以上はReact Error Boundaryに投げる
return (error as any)?.status >= 500;
},
},
},
});
個別のエラーは useEffect で error を監視してトーストを表示する、という2段構えにした。これが今のところ落ち着いた形。
v5で追加された useSuspenseQuery
v5から useSuspenseQuery が正式に追加された。
const { data } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
React Server Componentsを使っているプロジェクトでは特に相性がよく、Next.js App Routerとの組み合わせでも使えるシーンが増えてきた。
Next.jsのデプロイ環境を選ぶ段階から、TanStack Queryを採用するかどうかを技術選定に含めておくと、後から入れ替えるより楽だと思う。
正直しんどかったポイント
「1ヶ月使って全部最高でした」は嘘になるので、正直なところも書いておく。
- v5へのmigrationコストが思ったより高かった。既存プロジェクトへの導入は慎重に
- DevToolsが重い。開発環境ではキャッシュ状態が可視化できて便利だが、パフォーマンスに影響が出ることがある
- ドキュメントの量が多すぎる。v5から追加された概念(mutations、optimistic updates、prefetching、suspense…)を全部把握するのに延べ4〜5時間かかった(正確には4時間13分。ログ見た)
1ヶ月使って出した結論
「useEffectでfetchを書きたくない」という人にはTanStack Queryが最強。 ただし学習コストは覚悟すること。
こんな人におすすめ:
- Reactでサーバーデータの取得・更新・キャッシュをしっかり管理したい
- Optimistic Updateやページネーションが複雑な要件がある
- TypeScriptで型安全にデータフェッチを管理したい
こんな人はSWRからでもいい:
- とにかくシンプルに始めたい
useSWRで十分なシンプルな取得がメイン- バンドルサイズを小さく保ちたい
- Next.js App Routerでサーバーフェッチがメイン
プログラミング学習のロードマップでも触れているが、こういった「どっちを使うか」の技術選定は、プロジェクトの要件と照らし合わせて判断するのが正解だ。正解は文脈によって変わる。
よくある質問
Q: TanStack QueryはNext.js App Routerと相性はいいですか?
A: 基本的には相性は良いですが、App RouterのServer ComponentsではTanStack Queryは使えません(クライアントコンポーネント専用)。App Routerを使っている場合、サーバーサイドのデータフェッチはNext.jsのfetch/Server Componentsに任せて、クライアントサイドのインタラクティブな操作にTanStack Queryを使う、という使い分けが現在の主流です。
Q: React QueryとTanStack Queryって別物ですか?
A: 同じです。React Query v4からTanStack Queryに名称が変更されました。TanStackが提供するライブラリ群の一つとして再編されたためで、パッケージ名は @tanstack/react-query になっています。機能は継続しており、React Query v5 = TanStack Query v5と思って問題ありません。
Q: v4からv5への移行は大変ですか?
A: 破壊的変更が多いので、既存のコードが多いプロジェクトだと数日かかる可能性があります。特に onSuccess/onError の削除、クエリキーの形式変更、cacheTime → gcTime のリネームの影響範囲が大きいです。公式のMigration Guideとcodemods(自動移行スクリプト)があるので、まずそれを使うと楽になります。
Q: SWRの方がシンプルと言っていたけど、Next.js案件ならどっちを選ぶべきですか?
A: App Routerを採用しているNext.js 14以降なら、サーバーフェッチはServer Componentsに任せて、クライアントサイドの操作が少なければSWRで十分です。Pages Routerだったり、クライアントサイドのデータ操作が複雑になりそうな場合はTanStack Queryを選ぶのが無難です。どちらも最初は小さなコンポーネントで試してから採用を判断するのをおすすめします。
【2026/04/20 追記】
v5.29がリリースされ、useQueries のsuspenseサポートが改善されました。複数クエリを並列でsuspenseしたいユースケースに対応しやすくなっています。また、React 19との正式対応がロードマップに含まれているとのことで、今後もアップデートに注目です。
まとめ:1ヶ月の総評
TanStack Query v5は、データフェッチングの複雑さをライブラリ側で抽象化してくれるという点で、今のReact開発に必要なツールだと思います。
v4からv5の変更は大きかったけど、変更の方向性は正しいと感じる。特に onSuccess/onError の削除は「副作用は明示的に書け」というReactの思想と一致している。
TypeScript + Reactの組み合わせで開発するフロントエンドエンジニアには、一度ちゃんと向き合う価値のあるライブラリだと断言できます。