TanStack Query vs SWR vs RTK Query in 2026: Which Data-Fetching Library to Pick
Three Libraries for One Hard Problem
Fetching data sounds trivial — call an API, show the result. It stops being trivial the moment two components request the same data, a mutation needs to update the UI before the server confirms, a list has to paginate, or the screen has to stay fresh while the user is looking at it. That's when hand-rolled useEffect fetching turns into a pile of loading flags, race conditions, and stale UI.
In 2026, three libraries own the job of managing that server state in React, and they make different bets:
useSWR hook, a tiny footprint, and stale-while-revalidate defaults. From the Next.js team, and a natural fit for read-heavy apps.Pick the wrong one and the symptoms are familiar: you bolt a second heavyweight dependency onto an app that only displays data, you outgrow SWR's simplicity halfway through a complex mutation flow, or you adopt all of Redux just to get its query layer.
This guide compares all three through two lenses: which is best to build on, and which produces a codebase that's clean to hand off or sell.
Analytics data and charts displayed on a screen
First, The Distinction That Makes This Whole Category Make Sense
Before comparing features, the single most important point: these three libraries manage server state, not client state — and those are different problems.
Server state needs caching, deduplication, background refetching, and invalidation. The most common architecture mistake in React apps is storing fetched API data in a client-state store by hand, which forces you to reinvent all of that — badly. TanStack Query, SWR, and RTK Query exist precisely so you don't.
Get this split right and most "our state management is a mess" complaints disappear: a data-fetching library for anything from an API, a light client-state library for browser-only state. That's the wider point of picking a coherent tech stack for web apps in 2026.
At a Glance
| TanStack Query | SWR | RTK Query | |
|---|---|---|---|
| First released | 2019 (as React Query) | 2019 | 2020 |
| Comes from | TanStack (Tanner Linsley) | Vercel (Next.js team) | Redux team |
| Core API | `useQuery` / `useMutation` | `useSWR` | Auto-generated hooks |
| Bundle size | Larger of the three | Smallest | Ships with Redux Toolkit |
| Mutations | First-class, rich | Basic (mutate / trigger) | First-class |
| Cache invalidation | By query key | By key / revalidation | Cache tags |
| DevTools | Best-in-class | Minimal | Via Redux DevTools |
| Requires a store/provider | Provider only | No provider needed | Redux store |
| Multi-framework | Yes (Vue, Svelte, Solid…) | React-focused | React (Redux) |
| Best for | Full-featured default | Lightweight, read-heavy | Existing Redux apps |
Note: all three are actively maintained and their APIs evolve. Treat this table as a map, not a spec sheet — verify current behaviour against each project's official docs before you commit.
The Design Decision That Explains Each One
Almost every practical difference follows from one bet each project made.
TanStack Query: treat server state as a first-class problem worth a full toolkit
TanStack Query's bet is that server state is complex enough to deserve a complete, dedicated engine — not a thin fetch wrapper. So it ships caching keyed by a query key, automatic deduplication of identical in-flight requests, background refetching on window focus and reconnect, stale-time and garbage-collection controls, retries, pagination and infinite queries, and mutations with optimistic updates and rollback.
// TanStack Query — a query and a mutation that invalidates it
import {
useQuery,
useMutation,
useQueryClient,
} from "@tanstack/react-query";
function Todos() {
const qc = useQueryClient();
const { data, isPending, error } = useQuery({
queryKey: ["todos"],
queryFn: () => fetch("/api/todos").then((r) => r.json()),
});
const addTodo = useMutation({
mutationFn: (title: string) =>
fetch("/api/todos", { method: "POST", body: title }),
// refetch the list once the server confirms
onSuccess: () => qc.invalidateQueries({ queryKey: ["todos"] }),
});
if (isPending) return <p>Loading…</p>;
if (error) return <p>Something went wrong.</p>;
return <List items={data} onAdd={(t) => addTodo.mutate(t)} />;
}What you get is the widest feature set and the best DevTools in the category — an inspectable panel showing every query, its cache state, and its fetch status. What you pay is the largest bundle of the three and more API surface to learn. It earns its place as the default when your app does real work with server data, not just displays it.
SWR: make the common case a one-liner
SWR's bet is the opposite — that for most screens, you just want fresh-enough data with almost no ceremony. Its name is its strategy: stale-while-revalidate. Return the cached value instantly, refetch in the background, update when the new data arrives.
// SWR — the whole thing is one hook
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
function Profile() {
const { data, error, isLoading } = useSWR("/api/user", fetcher);
if (isLoading) return <p>Loading…</p>;
if (error) return <p>Failed to load.</p>;
return <span>Hello, {data.name}</span>;
}There's no provider to wire up, the bundle is the smallest of the three, and the defaults — dedup, focus revalidation, retry — are sensible out of the box. Coming from the Next.js team, it pairs naturally with Vercel-hosted apps.
The honest caveat: SWR is deliberately lean. Its mutation story is basic compared to TanStack Query's, and complex flows — heavy optimistic updates, intricate cache manipulation, infinite queries with fine control — are where you start writing code that TanStack Query would have handled for you. SWR is superb until you outgrow it.
RTK Query: fold data fetching into the store you already have
RTK Query's bet only pays off under one condition: you're already using Redux Toolkit. If you are, adding a separate data-fetching library means two systems for state. RTK Query instead builds fetching into the Redux store — you define an API slice with endpoints, and it auto-generates typed hooks and manages the cache with a tag system.
// RTK Query — endpoints defined once, hooks generated for you
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
tagTypes: ["Todo"],
endpoints: (build) => ({
getTodos: build.query({ query: () => "todos", providesTags: ["Todo"] }),
addTodo: build.mutation({
query: (title) => ({ url: "todos", method: "POST", body: { title } }),
invalidatesTags: ["Todo"], // auto-refetches getTodos
}),
}),
});
export const { useGetTodosQuery, useAddTodoMutation } = api;For a team standardized on Redux Toolkit, this is genuinely elegant: one store, cache tags that invalidate cleanly, and no extra dependency. The trap is adopting Redux *just* to get RTK Query — you take on Redux's setup, boilerplate, and bundle weight to solve a problem TanStack Query and SWR solve standalone with less ceremony. RTK Query is the right answer to "we already use Redux," not to "how should I fetch data."
Mutations and Invalidation: Where The Real Differences Live
Reading data is the easy 20%. The other 80% is what happens when the user *changes* something and the UI has to stay correct. This is where the three separate most.
useMutation with onMutate for optimistic updates, automatic rollback on error, and invalidateQueries to refetch affected data by key. Fine-grained cache updates are first-class.mutate to revalidate or optimistically update a key. It handles the common cases well, but complex optimistic flows take more manual work than in the other two.If your app is mutation-heavy — dashboards where users constantly edit, CRUD-heavy admin panels, anything with optimistic UI — weight TanStack Query or RTK Query. If it's mostly reads with occasional writes, SWR's lighter model is plenty.
Developer working on data-heavy dashboard code
The Next.js App Router Changes The Question
Here's the shift that catches people out: in the Next.js App Router, a lot of data fetching moves to the server, *before* any of these libraries is involved.
Server Components fetch data on the server and render it into the initial HTML. Mutations run through Server Actions. React's own fetch cache and revalidation handle a meaningful slice of caching. For a page that simply displays server data and never mutates it interactively, you may not need TanStack Query or SWR at all.
Where a client data-fetching library still earns its keep is interactive client state that talks to the server:
The 2026 pattern that gives you both worlds: fetch the initial data in a Server Component, then hydrate it into TanStack Query on the client. The first paint is server-rendered and fast; subsequent interactions get client-side caching, refetching, and mutations. SWR supports a similar fallback-data hydration. Whichever you pick, let the server do the first fetch and let the client library take over for interaction — don't duplicate the work.
Bundle Size and Performance: What's Real
Bundle size is a genuine, repeatable difference. SWR is the smallest — a lightweight hook you can drop into a marketing site without a second thought. TanStack Query is larger because it does more; on a data-heavy app already shipping hundreds of kilobytes, the difference is noise, but on a lean site it's worth noticing. RTK Query doesn't add a standalone data-fetching bundle — it rides along with Redux Toolkit, which itself is the heavier baseline.
Runtime behaviour is more alike than different: all three deduplicate identical requests, cache by key, and revalidate in the background. The performance wins that matter come from configuration, not from the library choice — a sensible staleTime so you don't refetch on every mount, precise query keys so caches don't collide, and not over-fetching. The honest framing: for the vast majority of apps, the library isn't your bottleneck. Your API latency, your render tree, and unmemoized expensive components are.
TypeScript: All Three Are Good
In 2026 all three have strong TypeScript support, with different feels.
queryFn return type; data is typed end to end, and the discriminated-union status (isPending, isError, isSuccess) narrows nicely.useSWR call is usually effortless.None will fight you. If you want the language-level reasoning behind caring about this at all, see TypeScript vs JavaScript. And because your data types usually originate at the database, this pairs closely with your ORM choice — see Drizzle vs Prisma for where those types start their journey.
DevTools and Debugging
If you debug data flow often, TanStack Query wins this clearly. Its DevTools panel shows every query, its exact cache state, whether it's stale or fetching, and lets you trigger refetches and inspect data live. For a complex app, that visibility saves real hours.
RTK Query is inspectable through the Redux DevTools — you see the cache in the store, action log included — which is powerful in a Redux context. SWR's tooling is the most minimal; it does the job but there's less to look at.
For a data-heavy dashboard with many interdependent queries, TanStack Query's DevTools are a genuine reason to weight it.
Lock-In and Handoff: What Someone Inherits
If you're building something you'll hand to a client, sell on a marketplace, or leave for a team, your data layer is a big part of what they inherit.
Two developers reviewing code together at a desk
Portable regardless of library: your API, your components, your business logic. All three libraries wrap *access* to server data — the endpoints and the UI move with you.
Where the inheritance gets opinionated:
useQuery, which lowers perceived risk for a buyer and is the safest default for a template meant to be extended.The failure mode that actually hurts resale isn't the library choice — it's incoherence: API responses crammed into a client store, hand-rolled useEffect fetching mixed with a real query library, or mutations that don't invalidate their queries so the UI shows stale data after a save. Pick one approach, wire mutations to invalidate the right queries, and show real loading and error states. A coherent data story is part of what separates a template buyers trust from one they abandon — see what makes code production-ready for the rest of that checklist.
Which One for Which Project
Choose TanStack Query if:
Choose SWR if:
Choose RTK Query if:
If you're still unsure:
In the App Router, start by letting Server Components fetch what they can, and add a client library only for interactive server state. When you do, the honest default in 2026 is TanStack Query for its feature set and DevTools — reach for SWR when simplicity and bundle size matter more than power, and RTK Query only when you're already on Redux Toolkit.
The Bottom Line
There's no universal winner — there's a right data-fetching library for what you're building and who inherits it.
Whichever you choose, three habits outlast the decision: keep server data in a data-fetching library and browser-only state in a client-state library, invalidate the right queries after every mutation so the UI never shows stale data, and let the server do the first fetch in the App Router so your first paint is fast. Those cost an afternoon and save a month.
Ready to turn what you build into income? List your template or app on CodeCudos, see how data fetching fits the wider stack in our best tech stack for web apps in 2026 guide, settle the client-state question with Zustand vs Redux Toolkit vs Jotai, or make sure the whole codebase reads as production-ready.
