5 min read
I fetched one cached key on React 19 in jsdom: SWR 2.5.1 shipped 5,628 B gzip against 10,424 B for TanStack Query 5.104.1, and fifty cold keys mounted in a median 5.8 ms against 10.9 ms. First-paint fetch time was a near tie at about 5.7 ms vs 5.0 ms when the fetcher itself was free.
Short answer: pick SWR when bundle weight and a small stale-while-revalidate client matter most. Pick TanStack Query when you need a query cache with mutations, dependents and DevTools. No affiliate links in this post.
| Browser build (esbuild minify, React external) | Minified | gzip -9 |
|---|---|---|
| @tanstack/react-query (QueryClient, Provider, useQuery, useMutation, useQueryClient) | 35,235 B | 10,424 B |
| swr (useSWR, SWRConfig, mutate) | 11,939 B | 5,628 B |
SWR was roughly half the gzip weight on this import set. Installed trees on disk were about 6.8 MB under vs 552 KB for swr. For keeping that number in CI, see size-limit vs bundlesize.
| Metric (median of 7, 0 ms fetcher) | TanStack Query | SWR |
|---|---|---|
| First mount + fetch | 4.99 ms | 5.65 ms |
| getQueryData / Map.get (per read) | 0.822 µs | 0.051 µs |
| Forced refetch | 0.13 ms | 0.06 ms |
| Remount 200 warm subscribers | 37.09 ms (0 fetches) | 37.63 ms (0 fetches) |
| Mount 50 distinct cold keys | 10.94 ms (50 fetches) | 5.77 ms (50 fetches) |
Both libraries deduped correctly: 200 hooks on one warm key triggered zero new fetches. The 50-key cold mount is where SWR pulled ahead on this box. With a 5 ms artificial fetcher delay, first fetch medians were 10.12 ms (TQ) and 9.93 ms (SWR), so the library overhead disappears behind the network. For stubbing the network in tests, my msw vs nock notes still apply.
| Situation | My pick |
|---|---|
| Small app, few endpoints, care about KB | SWR |
| Need mutations, dependent queries, cache helpers | TanStack Query |
| Heavy multi-key dashboards on first paint | SWR was faster here on 50 cold keys |
| You already live in the TanStack ecosystem | TanStack Query |
| Want the smaller default client | SWR |
Feature surface is the real fork. TanStack Query gives you mutations, optimistic updates, infinite queries and a structured QueryClient. SWR stays closer to "fetch + cache + revalidate." Neither failed the dedupe check. If a swap leaves the old package installed, knip vs depcheck finds it. Runner choice for the suite around these hooks is covered in Jest vs Vitest, and jsdom quirks in jsdom vs happy-dom.
Bottom line: on this React 19 box, SWR 2.5.1 was 5.6 KB gzip against 10.4 KB for TanStack Query 5.104.1, and mounted 50 cold keys in 5.8 ms against 10.9 ms, with first-fetch times otherwise tied. Who should not switch: teams that already depend on TanStack mutations, query defaults and DevTools — the KB gap alone is not a reason to rip that out.
How this was made: I installed both clients on the ShopperCove box, timed mount/fetch/cache/refetch paths under jsdom, measured esbuild minify+gzip sizes and recorded the JSON. The write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 6 Oct 2026 ~00:48-00:51 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). @tanstack/react-query 5.104.1 vs swr 2.5.1, React 19.3.0, jsdom 27.4.0. Fixture /workspace/bench-tq-swr/: fake async fetcher returning one JSON payload; QueryClient retry off / staleTime Infinity for warm path; SWR revalidateOnFocus false, dedupingInterval 60s, custom Map provider. 7 trials after warm-up. Medians: first fetch 4.99 vs 5.65 ms; cache read 0.822 vs 0.051 µs (5k reads); fetchQuery/mutate refetch 0.13 vs 0.06 ms; 200 warm subscribers 37.09 vs 37.63 ms with 0 fetches; 50 distinct cold keys 10.94 vs 5.77 ms (50 fetches each). 5 ms fetcher delay: first 10.12 vs 9.93 ms, multi50 17.74 vs 12.52 ms. esbuild minify/gzip9 React external: TQ 35,235/10,424 B; SWR 11,939/5,628 B. Disk ~6.8 MB @tanstack vs ~552 KB swr. Raw: res-react.json + res-bundle.json. Not tested: mutations, infinite queries, SSR/hydration, Next adapters, RN, real browsers, DevTools. No affiliate.