Plate 38
TanStack Query vs SWR: 10.4 KB vs 5.6 KB Gzip
Aditya Challa5 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.
What I tested
- Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 00:48–00:51 IST.
- Versions: @tanstack/react-query 5.104.1, swr 2.5.1, React 19.3.0, jsdom 27.4.0, Node 22.20.0, esbuild for the bundle check. NODE_ENV=production.
- Fixture: /workspace/bench-tq-swr/. One JSON payload (). Fake async fetcher that increments a counter (0 ms and 5 ms delay modes). QueryClient with retry off, staleTime Infinity for the warm-cache path; SWR with revalidateOnFocus false and a 60 s dedupingInterval.
- Timing: 7 trials after a warm run. Metrics: first mount+fetch, 5,000 raw cache reads, forced refetch (fetchQuery / mutate), remount of 200 subscribers on a warm key, mount of 50 distinct cold keys.
- Findings: gzip 10,424 B (TQ) vs 5,628 B (SWR). First fetch median 4.99 ms vs 5.65 ms. Cache read 0.822 µs vs 0.051 µs. Refetch 0.13 ms vs 0.06 ms. 200 warm subscribers 37.1 ms vs 37.6 ms with 0 extra fetches. 50 cold keys 10.94 ms vs 5.77 ms (50 fetches each). With a 5 ms fetcher delay, first fetch 10.12 vs 9.93 ms.
- One surprise: raw cache reads were about 16× faster on SWR's Map than on QueryClient.getQueryData. Once a real network delay was added, first-fetch times converged.
- Not tested: mutations, infinite queries, prefetch, SSR/hydration, Next.js App Router adapters, React Native, real browsers, TanStack Query DevTools overhead, SWR infinite/local middleware.
How big is the client?
| 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.
Who wins first fetch and cache reads?
| 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.
Which one should you pick?
| 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.
Sources
- https://tanstack.com/query/latest
- https://www.npmjs.com/package/@tanstack/react-query
- https://swr.vercel.app/
- https://www.npmjs.com/package/swr
Related
- https://www.shoppercove.com/blog/msw-vs-nock
- https://www.shoppercove.com/blog/size-limit-vs-bundlesize
- https://www.shoppercove.com/blog/jest-vs-vitest
- https://www.shoppercove.com/blog/knip-vs-depcheck
- https://www.shoppercove.com/blog/jsdom-vs-happy-dom-vitest
- https://www.shoppercove.com/blog/biome-vs-eslint-prettier
- https://www.shoppercove.com/blog/react-19-3-view-transitions-fragment-refs-2026
- https://www.shoppercove.com/blog/oxlint-vs-eslint
Lab evidence
What I found running this
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.
Related links
Plate 12
Yarn Berry vs pnpm: 1.3s vs 0.24s Install
Yarn 4.9.2 (Berry PnP + node-modules linker) vs pnpm 12.9.1 on one 22-dependency React+Vite app. Isolated caches (Yarn enableGlobalCache false; pnpm --store-dir). Warm medians (5): Yarn PnP 1.307 s, Yarn nm 2.471 s, pnpm 0.240 s. Lock+empty medians 1.656 / 2.789 / 0.725 s. Clean disk: Yarn PnP 246 MB; Yarn nm modules 189 MB; pnpm hardlink project 167 MB. Fixture /workspace/bench-yarn-pnpm/. Distinct from pnpm-vs-npm-vs-bun (no Yarn).
5 Oct 2026
Plate 48
Zod vs Valibot: 1.14M vs 0.97M Parses
zod 4.6.5 vs valibot 1.5.0 on one nested user schema (uuid/email/age/tags/profile/enum). Warm medians after 2k warmup + 9×50k: zod.parse 1,144,494 ops/s vs valibot.parse 968,187 (~1.18×). safeParse fail: valibot 697,760 vs zod 415,523 (~1.7×). esbuild minify+gzip9: 452,999/92,636 B vs 85,126/15,422 B. Package trees ~6.1 MB vs ~1.9 MB. Fixture /workspace/bench-zod-valibot/.
5 Oct 2026
Plate 88
Sharp vs Jimp: 112ms vs 366ms Resize
sharp 0.35.5 vs Jimp 1.6.1 on one 1920×1080 JPEG → width 800 JPEG q80. Warm medians (7 rounds after 1 warm-up): sharp 112 ms / 142,435 B vs Jimp 366 ms / 269,590 B (~3.3×). Fixture /workspace/bench-sharp-jimp/.
5 Oct 2026