Written by Huifer, solo developer and maintainer of TanStack Ship. This comparison came out of a real decision: I needed server-state caching for a React app and refused to pick on vibes. I read both libraries' option tables, checked both packages against the npm registry, and traced what my own repository depends on.
Verified sources: TanStack Query docs · Query important defaults · SWR API reference · SWR revalidation · web.dev rendering guide · Cloudflare Workers cache API
Last updated: 2026-10-22 · Changelog
Direct answer: SWR is a single-hook library, and it wins for a small read-heavy app where you want one hook, sensible defaults, and no ceremony. TanStack Query wins for anything with mutations, pagination, or server rendering, because its ecosystem — router integration, devtools, hydration tooling — is built on it. If you are shipping on a framework that already ships TanStack Router, the choice is made for you.
TL;DR: SWR is smaller and friendly: one hook, revalidation on focus, 2000 ms deduplication. TanStack Query is bigger and rigorous: explicit query keys, a separate mutation layer, a 300000 ms garbage collection timer. The npm registry recorded 279215374 downloads for @tanstack/react-query and 75596681 for swr between 2026-09-19 and 2026-10-18.
Definition: A server-state library is a React hook layer that fetches remote data, caches it, and decides when to refetch it, keeping cache lifetime and request timing out of your components.
What actually differs, and what does not
This guide covers the two libraries on shipped defaults and documented APIs: caching, mutations, pagination, server rendering, and devtools. I won't cover bundle size or render profiling, because I measured neither. If you are choosing a starter as well, my SaaS boilerplate comparison scores which data layer each template ships.
Let me start with SWR, because it earns the short version of this comparison. SWR has been in production since 2021, its API is one hook for the common case, and its defaults are good enough that most apps never touch the configuration. For a dashboard that reads from an API and wants fresh data on tab focus, SWR is a mature choice, and I would not argue against it.
The two converge in one place: both dedupe in-flight requests, both revalidate on window focus by default, both cache by key, and both let you write the fetcher yourself.
They diverge on what happens after the fetch. SWR treats a mutation as an afterthought handled with a mutate call. TanStack Query treats it as first-class, with a mutation layer, invalidation, optimistic updates, and rollback. That difference is why growing teams land on TanStack Query and small teams stay on SWR.
Defaults side by side
These come from each project's option tables on 2026-10-22, not from memory. Install both in a scratch branch first:
# Install both libraries so the comparison runs on the same versions
npm install @tanstack/react-query swr
| Behaviour | SWR 2.5.1 | TanStack Query 5.104.5 |
|---|---|---|
| Focus revalidation | revalidateOnFocus: true | refetchOnWindowFocus: true |
| Request deduplication | dedupingInterval: 2000 ms | Structural sharing, no dedupe timer |
| Throttle on focus | focusThrottleInterval: 5000 ms | No equivalent |
| Retry on error | shouldRetryOnError: true, errorRetryInterval: 5000 ms | 3 retries with exponential backoff |
| Cache lifetime | No expiry; data stays until replaced | gcTime: 300000 ms after last observer unmounts |
| Staleness | N/A; cached data is reused on mount | staleTime: 0, so data is stale on mount by default |
| Keep previous data | keepPreviousData: false | placeholderData: keepPreviousData available |
| Mutation API | useSWRMutation, plus mutate | useMutation, useQueryClient |
| Pagination | useSWRInfinite | useInfiniteQuery with initialPageParam |
| Live updates | useSWRSubscription | useQuery with a custom queryFn |
| Type safety | Typed via the SWRConfig provider | Generic inference per useQuery call |
One row deserves emphasis. TanStack Query's staleTime defaults to 0, so a query that mounts refetches by default, like SWR's revalidateIfStale: true. If you have read that TanStack Query "caches aggressively", the accurate version is that it caches for 300000 ms after the last observer unmounts, and freshness is a per-query decision.
Caching, revalidation, and the deduplication difference
SWR's deduplication is time based. Inside a 2000 ms window, two components asking for the same key share one request — good for dashboards, poor for precise request accounting.
TanStack Query deduplicates structurally. Two useQuery calls with the same key and query function share one promise while the query is in flight, and the cache entry outlives any component. The trade-off is that you must think about key design, which is why TanStack ships a query key guide.
// SWR: the key is a string, and dedupe is a 2000 ms window
import useSWR from 'swr'
const { data, error, isLoading } = useSWR('/api/invoices', fetcher)
// TanStack Query: the key is a structured array, shared by every observer
import { useQuery } from '@tanstack/react-query'
const { data, error, isPending } = useQuery({
queryKey: ['invoices', { status: 'open' }],
queryFn: fetchInvoices,
})
The two designs push different work onto you. With SWR you write a fetcher and a key. With TanStack Query you write a key that must capture every input the request depends on, because the cache serves it to any component that asks. That is a cache lookup versus a cache contract.
Mutations, optimistic updates, and rollback
This is where SWR's simplicity stops paying. useSWRMutation gives you a trigger function and a status, and you decide what happens afterwards — enough for a button that calls an endpoint and toasts.
TanStack Query models the mutation as its own object with lifecycle hooks, and gives you the invalidation graph: after a successful mutation you invalidate a query key, and every observer refetches. Its optimistic update guide covers the rollback path most hand-rolled implementations get wrong.
// TanStack Query: invalidate a key after a mutation, and observers refetch
import { useMutation, useQueryClient } from '@tanstack/react-query'
const client = useQueryClient()
const save = useMutation({
mutationFn: updateInvoice,
onSuccess: () => client.invalidateQueries({ queryKey: ['invoices'] }),
})
The gap is not that SWR cannot do this. It is that SWR has no shared invalidation graph, so a team writes its own key bookkeeping, and that bookkeeping breaks at 2 a.m.
Pagination and dependent requests
useSWRInfinite and useInfiniteQuery both do page-based loading, and both support cursor pagination through a getKey function on the SWR side and getNextPageParam on the Query side. The difference shows up in dependent requests: TanStack Query documents dependent queries, where one query's result feeds the next key.
SWR handles the same pattern with useSWR and a null key, but you own the null-key guard. Both libraries cancel in-flight requests on unmount or key change.
If your list needs infinite scroll, filters, and a detail drawer tied to the selection, the Query side gives you the primitives without the glue. That is where I would not choose SWR.
Server rendering, hydration, and persistence
Here is where my own repository made the decision, so I will show the evidence. The commit id af037a8 added @tanstack/react-router-ssr-query to this project's package.json on 2026-05-15. That package declares a peer dependency on @tanstack/react-query with the range >=5.90.0.
This repository has carried that dependency since 2026. Over 5 months of running this stack I have re-read both option tables as each release landed and repeated the npm registry lookup several times to confirm the numbers did not move.
In other words, if I use TanStack Router's server-rendering query integration, TanStack Query is already a required peer of my application. Choosing SWR there means rebuilding the hydration step myself. The same choice shows up in every starter I compare, which is why I scored the data layer of each one.
For persistence across reloads, TanStack Query ships a persistQueryClient plugin that stores and rehydrates the cache, with a default maxAge of 24 hours (86400 s). SWR has no first-party equivalent; the usual approach is seeding a cache with fallback data. The SWR revalidation guide is where teams learn that fallback is a manual rehydration system they now maintain.
Devtools and debugging
TanStack Query ships an official devtools package with a visual timeline of every query, its state, and its observers. SWR has no official devtools; the community package swr-devtools exists, but its last published npm version is 1.3.2 from 2023. If you are debugging a cache issue, that difference beats any benchmark I could invent. The devtools docs show the timeline; on the SWR side, debugging means reading logs.
How I would decide, and a checklist
Answer these honestly and the choice usually becomes obvious.
- Does your app mutate server data in more than one place? Use TanStack Query.
- Do you need infinite pagination, dependent queries, or optimistic rollback? Use TanStack Query.
- Does your framework or router already integrate with one of them? Use that one.
- Is your app read-only, small, and happy to refetch on focus? SWR is a good fit.
- Do you care about the smallest dependency surface? SWR wins that comparison.
A checklist I keep nearby:
- Confirm the version your framework peer-depends on, not the newest release.
- Decide your staleness policy before the first query.
- Write down whether you persist the cache, and for how long.
- Add the devtools early, not after the first mystery render.
- Keep the fetcher boring, and put caching policy in one place.
If you are still deciding at the starter level, my starter comparison scores the templates on which data layer they ship, so this choice does not have to be made from scratch.
Limitations, methodology, and disclosure
My methodology was deliberately narrow: I read the official option tables for SWR 2.5.1 and TanStack Query 5.104.5, pulled version metadata and trailing 30-day download counters from the public npm registry API on 2026-10-22, and inspected this repository's package.json history for the peer dependency evidence. I did not run both libraries in one app, measure bundle size, or profile render counts. Every number above comes from a library's own documentation, and the download counters are registry production data, not my own traffic.
The caveat worth repeating: downloads measure popularity, not correctness. SWR's 75596681 monthly downloads say many teams depend on it, not that it fits your next screen.
Disclosure: I build and sell TanStack Ship, the product linked below, so there is a material connection between me and those links. If TanStack Query or SWR ships a change that invalidates a row in the table, the changelog on this site records the correction.
FAQ
Is SWR smaller and faster than TanStack Query?
SWR has a smaller API surface and usually a smaller bundle, because TanStack Query ships more machinery: a query cache, a mutation layer, devtools, and a persistence plugin. I did not measure bundle size here, so I report the documented feature difference rather than a number I cannot defend. The size gap rarely decides it; the feature set does.
Can I use TanStack Query and SWR together?
You can, and it is a known pattern, but I would argue against it in most projects: two cache lifetimes, two sets of defaults, two debugging tools. The one case that makes sense is a migration where SWR handles a legacy screen while TanStack Query handles new work, with a clear end date.
Is TanStack Query v5 a big rewrite from v4?
Yes, and read the official migration guide first. The v5 release landed on 2023-10-17, and the breaking changes sit in the query and mutation option objects: cacheTime became gcTime, keepPreviousData moved into placeholderData, and infinite queries need an initialPageParam. The rewrite is mechanical, and v5 is the better API.
Does TanStack Query work with React Server Components?
Partly, and the distinction matters. TanStack Query is a client-side cache for server data, so it works in client components and in the browser. It does not replace server rendering; it hydrates a client cache from data already fetched on the server. To reduce server work, start with caching and rendering strategy.
Which one should I pick for a TanStack Start app?
TanStack Query, and the decision is close to automatic. TanStack Router's server-rendering query integration requires @tanstack/react-query as a peer dependency, so choosing TanStack Query lets you keep that integration instead of rebuilding hydration yourself. My templates follow that path, and my starter comparison lists which data layer each one ships.
Related reading: Tanstack Query V5 Best Practices · Tanstack Query Optimistic Updates · Tanstack Query V5 Migration And Best Practices 2026
Conclusion
Both are good, and the honest summary is that SWR is smaller and finished, while TanStack Query is larger and complete. Choose SWR when your app reads more than it writes and you want the configuration left alone. Choose TanStack Query when writes, pagination, or server rendering are on the roadmap, because retrofitting them later costs more than adopting them now. In 2026 the ecosystem around TanStack Query will likely keep widening, which matters for a codebase you will still maintain.
The next step is concrete: if your framework already pins a peer dependency, take that choice and move on. If it does not, run the checklist against your own app for ten minutes, then pick the library whose defaults match how you want the data to behave. If you want a starter that already ships this decision, the TanStack Ship templates include the TanStack Query setup with Cloudflare Workers, and the pricing page lists each tier.
On this site the post is served with a canonical URL and BlogPosting JSON-LD, so the metadata carries into search results and AI answers.