EmpireUI
Get Pro
← Blog9 min read#tanstack query#swr#react

TanStack Query vs SWR in 2026: Data Fetching Library Showdown

TanStack Query v5 vs SWR v2 — a no-nonsense breakdown of caching, mutations, DevTools, and bundle size to help you pick the right one in 2026.

Two code editors side by side showing React data fetching code

Still Debating This in 2026?

Yeah, you'd think this debate would be settled by now. TanStack Query (previously React Query) and SWR have both been around long enough that most teams have strong opinions — but the libraries have kept evolving, and the gap between them has shifted in interesting ways since 2024.

TanStack Query hit v5 in late 2023 and the API changed enough that upgrading from v4 was a real migration, not a bump. SWR stayed calmer, landing v2 with incremental improvements and maintaining its reputation as the "just works" option. So where does that leave you in 2026?

Honestly, the right answer still depends on what you're actually building. A blog with mostly GET requests? SWR is genuinely fine. An enterprise dashboard with optimistic updates, complex invalidation logic, and offline support? You'll probably end up fighting SWR's limited mutation model inside of a month.

This isn't a benchmarks-only post. We're going to look at real API patterns, bundle cost, DevTools quality, and where each library actually hurts you.

Core Mental Models Are Very Different

SWR's entire pitch is in its name — stale-while-revalidate. You get data from cache immediately, then the library fetches fresh data in the background and updates the UI. Simple. Vercel built it for their own use cases, and that heritage shows: it's excellent for read-heavy apps where you want Vercel-style instant page loads.

TanStack Query thinks in terms of *server state*. Queries have lifecycle states (pending, error, success, paused), and the library manages when to refetch, how long to consider data fresh, and how to garbage-collect stale entries. That sounds like more complexity — because it is — but it maps directly onto how server data actually behaves.

The practical difference shows up fast when you do mutations. In SWR, mutate is both the manual cache update function *and* how you trigger revalidation. It's a little weird. In TanStack Query, useMutation is a completely separate primitive with its own onSuccess, onError, onSettled callbacks and a clear relationship to useQuery via invalidateQueries. After spending two days debugging a SWR mutation chain in a 2025 project, I'd argue TanStack Query's model is just cleaner for anything beyond basic CRUD.

Worth noting: SWR's useSWRMutation hook (added in v2) did narrow this gap. It gives you an explicit trigger pattern instead of auto-fetching. But it still doesn't give you the query key invalidation graph that TanStack Query has.

API Ergonomics: What Day-to-Day Code Actually Looks Like

Let's be concrete. Here's a typical data fetch with each library:

// SWR
import useSWR from 'swr'

const fetcher = (url: string) => fetch(url).then(r => r.json())

function UserProfile({ id }: { id: string }) {
  const { data, error, isLoading } = useSWR(`/api/users/${id}`, fetcher)

  if (isLoading) return <Skeleton />
  if (error) return <ErrorState />
  return <Profile user={data} />
}
// TanStack Query
import { useQuery } from '@tanstack/react-query'

function UserProfile({ id }: { id: string }) {
  const { data, error, isPending } = useQuery({
    queryKey: ['users', id],
    queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()),
  })

  if (isPending) return <Skeleton />
  if (error) return <ErrorState />
  return <Profile user={data} />
}

The SWR version is slightly less code. But look at TanStack Query's queryKey array — that's the thing that makes complex apps manageable. You can invalidate ['users'] broadly, or ['users', id] specifically, or run queryClient.setQueryData to update the cache after a mutation without a refetch. SWR doesn't have a comparable query graph.

Quick aside: TanStack Query v5 made queryKey required and moved to a single object argument (no more positional params). If you're migrating from v4, that's a find-and-replace job, but it does make the API more consistent. SWR changed basically nothing in its core API between v1 and v2, which is either reassuring or boring depending on your mood.

One more thing — SWR has a use option that integrates with React Suspense out of the box with a single boolean flag. TanStack Query supports Suspense too via useSuspenseQuery, but the ergonomics for Suspense-first apps slightly favor SWR here.

Caching, Invalidation, and the Offline Story

SWR's cache is per-key. That's it. You get mutate('/api/users/1') to revalidate a specific URL, and mutate(() => true) to revalidate everything. For simple apps this is totally fine. For apps where a single action should cascade updates across multiple resource types — say, creating a comment that should update both the comments list *and* the post's comment count — you're writing boilerplate to keep things in sync.

TanStack Query's query key invalidation is genuinely powerful. You define keys with arrays like ['posts', postId, 'comments'] and then call invalidateQueries({ queryKey: ['posts'] }) to blow away every query that starts with posts. Or you target ['posts', postId] precisely. This hierarchical invalidation is something you don't appreciate until you're debugging a cache consistency bug at 11pm.

// After creating a comment in TanStack Query
onSuccess: () => {
  // Invalidates both the comments list and the post details
  queryClient.invalidateQueries({ queryKey: ['posts', postId] })
},

Offline support is where TanStack Query really separates itself. It has a built-in NetworkMode that you set to 'offlineFirst' or 'online', and queries that fail due to network errors get queued and retried automatically when the connection restores. SWR has a isOnline integration via window.addEventListener('online', ...) but it's more of a revalidation trigger than true offline queuing. In practice, if you're building a PWA or anything that needs to work on flaky connections, TanStack Query is the better foundation.

That said, most web apps don't need offline queuing. If yours doesn't, you're paying TanStack Query's complexity tax for nothing.

Bundle Size, DevTools, and the Real Cost

Numbers as of mid-2026: TanStack Query core is around 12.5 kB minified + gzipped. SWR is around 4.3 kB. That's a real difference if you're building something where every kilobyte matters — a marketing page, a widget that embeds in third-party sites, anything where you've already tuned your bundle down to the wire.

For most Next.js or Vite apps shipping 200–500 kB of JS already? The 8 kB difference is noise. Don't make your architectural decision based on this. That said, it's worth knowing: TanStack Query ships its DevTools as a separate package (@tanstack/react-query-devtools, roughly 15 kB), so you're tree-shaking it out of production anyway.

Speaking of DevTools — TanStack Query's DevTools panel is genuinely excellent. You get a query inspector, cache viewer, refetch triggers, and a timeline. SWR has a community devtools package but it's not officially maintained and the UX isn't comparable. If you've ever tried to debug a caching issue by sprinkling console.log everywhere, you'll understand why good DevTools matter.

Look, if your team is new to server state management, the TanStack Query DevTools alone will save you debugging hours that more than justify the bundle cost. It's one of those tools where you wonder how you lived without it.

Both libraries work fine with Empire UI's component system — you'd pair useSuspenseQuery with a skeleton component from the library the same way regardless of which fetching library you choose. The UI layer is completely decoupled.

Mutations, Optimistic Updates, and Where SWR Falls Short

Optimistic updates are where the gap gets uncomfortable for SWR. The pattern requires you to manually update the SWR cache, handle the rollback on error, and then trigger revalidation — all in your component code or in the fetcher config. It works, but it's verbose and error-prone.

// Optimistic update in SWR — more boilerplate than you'd want
const { trigger } = useSWRMutation('/api/todos', addTodo, {
  optimisticData: (current) => [...(current ?? []), newTodo],
  rollbackOnError: true,
  populateCache: true,
  revalidate: false,
})
// TanStack Query — more explicit but also more composable
const mutation = useMutation({
  mutationFn: addTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    const previous = queryClient.getQueryData(['todos'])
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
    return { previous }
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previous)
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

In practice, the TanStack Query version is more code but it's also more obvious. Every step is explicit. The SWR version's populateCache / revalidate combination is clever but it's the kind of thing where you'll check the docs every time you write it. I've been writing SWR optimistic updates since 2022 and I still look it up.

If you're building anything with a rich interaction model — kanban boards, collaborative editing, real-time updates, shopping carts — the mutation primitives in TanStack Query are going to serve you better. For a SaaS dashboard with complex write operations, this alone is probably the deciding factor.

When to Pick Which One

Pick SWR if: you're building a content-heavy site or blog, your team already knows it, you want minimal setup overhead, or you're on a strict bundle budget. It pairs especially well with Next.js App Router since Vercel maintains both. For a landing page or marketing site pulling in a handful of endpoints, SWR is genuinely the right call — don't overcomplicate it.

Pick TanStack Query if: you have complex server state across many components, you need solid mutation ergonomics with optimistic updates, you're building a dashboard or app with multiple interconnected data types, or you want first-class DevTools. The react performance guide touches on how query caching patterns affect render cycles — and TanStack Query gives you more levers to tune that.

One pattern that's underrated: using TanStack Query's queryClient.prefetchQuery in your Next.js server components with nextjs-caching-strategies. You prefetch on the server, hydrate the client-side cache, and your user never sees a loading state for the initial page. SWR has a fallback prop for the same idea, but TanStack Query's dehydrate/hydrate API is more complete.

That said, there's no wrong answer if your team already has momentum with one of them. The switching cost — real migration time, retraining, updated tests — is usually higher than any technical advantage you'd gain. Only switch if you're genuinely hitting the other library's ceiling.

If you're building a new project from scratch in 2026, I'd default to TanStack Query. The ecosystem is larger, the docs are excellent, and @tanstack/react-query-devtools will pay for itself in the first week. You can always explore Empire UI components's interactive demos to see how they compose with different data-fetching approaches in practice.

FAQ

Is TanStack Query the same as React Query?

Yes. React Query was renamed to TanStack Query in v4 to reflect its framework-agnostic scope — it now supports Vue, Solid, and Angular too. The React-specific entry point is still @tanstack/react-query.

Can I use SWR with Next.js App Router?

You can, but SWR is a client-side library so you'd use it in Client Components. For server-side data in App Router, Next.js's built-in fetch caching or TanStack Query's SSR hydration pattern are better fits.

Does TanStack Query work without React?

Yes. Since v4 it has adapters for Vue, Solid, Svelte, and Angular. The core query client is framework-agnostic. SWR is React-only.

What's the difference between staleTime and gcTime in TanStack Query v5?

staleTime controls how long data is considered fresh before a background refetch triggers. gcTime (previously cacheTime) controls how long inactive query data sits in memory before being garbage collected. They're independent — you can have fresh data that's still cached after going inactive.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

TanStack Query vs Redux Toolkit: Server State vs Global StateTanStack Query vs SWR vs Apollo: Data Fetching Library ChoiceTanStack Query v5 in React: Data Fetching That Actually ScalesReact Data Fetching Patterns in 2026: Server, Client, Cache