---
title: "TanStack Query vs SWR"
slug: "tanstack-query-vs-swr"
category: "comparisons"
tags: ["comparisons", "tanstack-query", "swr", "react", "data-fetching", "state-management"]
status: "stable"
last_updated: 2026-05-14
summary: "Pick TanStack Query for richer cache control, mutations, and devtools; pick SWR when the app is small, lives on Vercel, and the workload is read-mostly."
related: ["[[frontend/react]]", "[[frontend/react-state-management]]", "[[frontend/react-suspense]]", "[[comparisons/zustand-vs-redux]]", "[[comparisons/server-vs-client-components]]", "[[frontend/index]]"]
---

## Overview

Use TanStack Query for any React app with mutations, optimistic updates, paginated lists, or background refetch logic. Use SWR when the app is small, read-mostly, and already on Vercel where SWR's defaults align with the platform. TanStack Query is the larger toolset; SWR is the lighter pattern. Both manage server state, not client state; pair either with Zustand or Redux for client state. See [[frontend/react-state-management]] for the wider rule set.

## When TanStack Query wins

TanStack Query is the right pick for any React app with non-trivial cache, mutations, or background sync behavior.

- Mutations: `useMutation` with `onSuccess`, `onError`, and `invalidateQueries` is the canonical pattern. SWR has `mutate` but lacks the lifecycle hooks; teams end up writing wrappers.
- Optimistic updates: `useMutation({ onMutate, onError, onSettled })` gives a typed rollback path. SWR optimistic UI is doable but the API is thinner.
- Query keys are arrays with structural equality; `['post', id]` invalidates only that post. SWR's string keys force string concatenation conventions.
- DevTools are best-in-class: see every active query, its cache state, last fetch time, and stale status. SWR DevTools exist but are less mature.
- Pagination, infinite scroll, prefetching, and dependent queries are first-class: `useInfiniteQuery`, `prefetchQuery`, `enabled` flag.
- Works outside React: `@tanstack/query-core` runs in Vue, Solid, and Svelte. Same mental model across the stack.
- Suspense and Server Components support is stable in v5; SWR's Suspense story is functional but quieter.

## When SWR wins

SWR is the right pick for small read-mostly apps, especially those already on Vercel.

- Smaller surface: `useSWR(key, fetcher)` is the whole API for 80 percent of use cases. New contributors are productive in an afternoon.
- Bundle: SWR is roughly 4 KB gzipped; TanStack Query is 13 to 15 KB. On a content site with one or two data hooks, that gap shows up in Time-to-Interactive.
- Vercel alignment: SWR is Vercel's library. Examples in the Next.js docs default to it; Vercel-hosted templates ship it pre-wired. The integration cost is zero.
- Stale-while-revalidate semantics map cleanly to the HTTP cache header pattern. If the API already sets `Cache-Control: stale-while-revalidate`, SWR mirrors it.
- Focus revalidation and reconnect refetch are default-on with sensible behavior; TanStack Query has the same but exposes more knobs to misconfigure.
- For a dashboard with a few read endpoints and no mutations: SWR is the smaller pick by every measure.

## Trade-offs at a glance

| Dimension          | TanStack Query                              | SWR                                       |
| ------------------ | ------------------------------------------- | ----------------------------------------- |
| Bundle (gzipped)   | 13 to 15 KB                                 | 4 KB                                      |
| Query keys         | Arrays with structural equality             | Strings or arrays via the key function    |
| Mutations          | First-class with lifecycle hooks            | `mutate()` plus manual wiring             |
| Optimistic updates | Typed; rollback path baked in               | Manual; less ergonomic                    |
| Devtools           | Excellent; visualizes every query           | Functional; smaller feature set           |
| Infinite scroll    | `useInfiniteQuery`                          | `useSWRInfinite`                          |
| Suspense           | Stable in v5                                | Supported; less documented                |
| Server Components  | Pre-fetch and hydrate pattern documented    | Works; less canonical guidance            |
| Cross-framework    | React, Vue, Solid, Svelte                   | React (and Next.js)                       |
| Hiring familiarity | High in React Native and dashboard teams    | High in Next.js teams                     |
| Sweet spot         | Apps with mutations and complex cache       | Read-mostly content and dashboards        |

## Migration cost

SWR-to-TanStack Query is the common direction once mutations or invalidation logic grows. The reverse is rare.

- SWR to TanStack Query: replace `useSWR(key, fetcher)` with `useQuery({ queryKey, queryFn })` and `mutate(key)` with `queryClient.invalidateQueries({ queryKey })`. The shapes are similar enough that a codemod handles roughly 70 percent. Plan one engineer-day per 30 hooks.
- TanStack Query to SWR: only worth it on a small app where the bundle line item shows up. Rewrite mutations to imperative `mutate()` calls and drop most lifecycle code. Plan one engineer-day per 30 hooks.
- Cheaper path: stay on the current library. The pain that justifies the switch usually shows up at the same time as a feature redesign; bundle the rewrite into that.

## Recommendation

- New React app with any mutations beyond a single form: TanStack Query.
- New small Next.js app on Vercel that is mostly reads: SWR. The defaults align.
- Dashboard with infinite scroll, optimistic edits, or background sync: TanStack Query.
- Marketing site with one or two data hooks: neither; use Server Components and skip client cache; see [[comparisons/server-vs-client-components]].
- React Native app: TanStack Query. The DevTools and offline patterns are documented for mobile.
- Existing SWR codebase that grew complex: migrate the mutations only, leave reads on SWR. Two libraries coexist fine.
- See [[frontend/react-state-management]] for the wider state strategy and [[comparisons/zustand-vs-redux]] for the client-state pair.

## Related

- [[frontend/react]]
- [[frontend/react-state-management]]
- [[frontend/react-suspense]]
- [[comparisons/zustand-vs-redux]]
- [[frontend/index]]
