Technology·13 min read

TanStack Query vs SWR vs Apollo Client: Data Fetching in 2026

A no-nonsense comparison of TanStack Query, SWR, and Apollo Client covering caching, mutations, suspense, bundle size, and how React Server Components reshape the decision.

Nate Laquis

Nate Laquis

Founder & CEO

The Data Fetching Wars Are Not Over

Every few months someone announces that the client side data fetching library is dead. React Server Components will kill it. Suspense will kill it. The framework will handle everything. And yet, every real production app I ship in 2026 still has TanStack Query, SWR, or Apollo Client sitting in the dependency graph, doing the unglamorous work of keeping server state in sync with the UI.

The reason is simple. Server state is not client state. It is stale the moment you read it, it can be updated by other users, it needs retries, deduplication, background refetching, pagination, optimistic updates, and cache invalidation. Frameworks have gotten better at the first request, but the second request onward still lives in the browser. That is where these three libraries fight.

I have shipped apps on all three. I have also ripped all three out of apps that never should have had them. This article is the opinionated field guide I wish someone had handed me three years ago. No vendor fluff, no "it depends" cop outs, just the tradeoffs that actually bite you in production.

Developer reviewing API response code on a monitor

Here is the short version before I defend it for three thousand words. TanStack Query is the default choice for almost everyone. SWR is the right call when you live inside the Vercel stack and want less ceremony. Apollo is only the right call when you have a real GraphQL schema, a real GraphQL team, and real GraphQL problems. Let us unpack why.

Caching Strategies: The Part That Actually Matters

Caching is the whole game. If you get caching wrong, nothing else matters. All three libraries are built around a normalized or keyed cache, but they make very different bets about how that cache should behave.

TanStack Query uses a flat key based cache where every query is identified by a serializable array. Data is stored as whole response blobs keyed by that array. It is stale-while-revalidate by default, with a configurable staleTime and gcTime. This design is boring in the best possible way. You almost never fight the cache. When you need to invalidate, you invalidate by key or key prefix and the library handles the rest. I have yet to see a team get confused by TanStack Query's cache model after a week of usage.

SWR uses a similar key based model but with less machinery around it. Keys are strings or arrays, data is whatever your fetcher returns, and revalidation happens on focus, on reconnect, and on interval. SWR is intentionally smaller and more opinionated. It does not ship a query client you instantiate, it does not have a persistent mutation cache, and it leans hard on the idea that you should just refetch when in doubt. For simple apps this is liberating. For complex apps it becomes a limitation.

Apollo Client is the odd one out. It uses a normalized cache based on __typename and id, which means two queries that return the same entity share storage. Update one, the other updates automatically. This is genuinely magical when it works, and genuinely miserable when it does not. You will write type policies, merge functions, and field policies. You will debug why a mutation did not update a list. You will learn the difference between cache.modify and writeQuery. The normalized cache is Apollo's superpower and its tax.

My verdict: TanStack Query wins on ergonomics, Apollo wins on raw cache power when you actually need it, and SWR wins when you want to stop thinking about caches altogether.

Mutations and Optimistic Updates

Reads are easy. Writes are where libraries earn their keep. A mutation is not just a POST request. It is a POST request plus cache invalidation plus optimistic UI plus rollback on error plus retry plus loading states. Every library handles this differently.

TanStack Query's useMutation hook is the cleanest of the three. You get onMutate for optimistic updates, onError for rollback, onSuccess for cache updates, and onSettled for cleanup. The pattern is explicit and teachable. Junior engineers can read a useMutation block and understand exactly what happens in each phase. Pairing it with queryClient.invalidateQueries gives you surgical control without normalized cache overhead.

  • Optimistic updates in TanStack Query: manual but predictable. You snapshot the old data, write the new data, and restore on failure.
  • Optimistic updates in SWR: built into the mutate function with an optimisticData option. Less code for the happy path, less control when things go wrong.
  • Optimistic updates in Apollo: handled via the optimisticResponse option on useMutation, which writes to the normalized cache and propagates everywhere automatically. Beautiful when it works.

Here is the rub with Apollo. Because the cache is normalized, an optimistic response can update a dozen components at once without you lifting a finger. But if your mutation response shape does not match exactly what the cache expects, you get subtle bugs where lists go stale or fields disappear. I have spent more debugging hours on Apollo optimistic responses than on every other part of these libraries combined.

Code editor showing network requests and mutations

For most teams, TanStack Query's explicit mutation model is the sweet spot. You write a few more lines, but every engineer on the team can reason about it. That is worth a lot.

Server State Sync, Suspense, and Streaming

Keeping client state in sync with server state is a distributed systems problem dressed up as a UI problem. The three libraries approach it differently.

TanStack Query refetches on window focus, on network reconnect, on interval, and on explicit invalidation. It deduplicates in-flight requests across components automatically. Version 5 added first class Suspense support with useSuspenseQuery, which integrates cleanly with React 19's concurrent rendering. You can stream data from the server, hydrate it on the client, and continue using the same query hooks without rewriting anything. This is the most mature streaming story of the three.

SWR pioneered the stale-while-revalidate pattern in React and still has the cleanest implementation of it. Focus revalidation, interval revalidation, and conditional fetching all feel natural. Suspense support exists but has lagged behind TanStack Query's. If you are on Next.js App Router and using RSC for initial loads, SWR's client side revalidation plugs in cleanly without fighting the framework.

Apollo has real time subscriptions baked in via the link architecture. If you need WebSocket powered live data, Apollo's subscription story is still the most battle tested. Its Suspense support landed in Apollo Client 3.8 and has matured, though it is less ergonomic than TanStack Query's version. Apollo also has the concept of fetch policies per query, which gives you fine grained control over when to hit the network versus the cache. This is powerful and confusing in equal measure.

If your app needs live updates over WebSockets, Apollo is the shortest path. If your app needs aggressive background revalidation with minimal config, SWR shines. If your app needs both plus Suspense plus streaming, TanStack Query is the safest bet.

DevTools, Developer Experience, and Bundle Size

Developer experience is not a tiebreaker, it is a primary criterion. You will spend hundreds of hours inside whichever library you pick. The tooling matters.

TanStack Query Devtools is the gold standard. You drop in a component, get a floating panel, and see every query in your cache with its status, data, and refetch controls. You can manually trigger refetches, invalidate queries, and inspect the raw data. I have caught production bugs in staging using these devtools that I would have missed otherwise. Apollo Client DevTools is a Chrome extension that shows your cache, queries, and mutations. It is good but feels dated next to TanStack Query's inline panel. SWR Devtools exists but is community maintained and less polished.

On bundle size, the gap is real:

  • SWR: around 4 kilobytes gzipped. The smallest of the three by a wide margin.
  • TanStack Query: around 13 kilobytes gzipped for the core, more with devtools in dev mode only.
  • Apollo Client: around 33 kilobytes gzipped minimum, often more once you add links and cache policies. This is the elephant in the room.

For a marketing site or a lightweight dashboard, Apollo's bundle size is hard to justify. For a complex enterprise app where you need normalized caching and subscriptions, the bundle cost is worth paying. For everything in between, TanStack Query is the honest middle ground.

API ergonomics also matter. TanStack Query's hooks are the most consistent. SWR's API is the most minimal. Apollo's API has the most surface area, which means more power but also more ways to misuse it. If you are building a team of five junior engineers, SWR or TanStack Query will ramp faster. If you are building a team of ten senior engineers who already know GraphQL, Apollo's complexity pays for itself.

GraphQL vs REST: The Elephant in the Architecture

Here is the uncomfortable truth. The library choice is downstream of the API choice. If you picked REST, Apollo is a terrible fit. If you picked GraphQL, SWR is a weird fit. TanStack Query is API agnostic, which is why it keeps winning.

Apollo Client assumes GraphQL. Its normalized cache, its optimistic response model, its fragment colocation, its type policies, all of it is built around the assumption that your API returns typed, nested, nullable fields with stable identifiers. If you have that, Apollo is genuinely the best tool in the category. If you do not, you are paying a massive tax for nothing. I have seen teams put Apollo in front of a REST API using apollo-link-rest, and it is always a mistake. The normalized cache gets in the way, the tooling stops helping, and you end up with the worst of both worlds.

SWR works with anything that returns a promise. It does not care what your API looks like. REST, GraphQL, tRPC, raw fetch to an edge function, it all works. But SWR does not help you with GraphQL specific concerns like fragments, variables, or typed responses. You will bolt those on yourself with codegen.

TanStack Query is in the same boat as SWR philosophically. It does not care about your API, it just caches whatever you return. With graphql-request or urql style clients plus TanStack Query on top, you get a perfectly workable GraphQL setup that does not lock you into Apollo's mental model. For teams that want GraphQL without Apollo, this combination is increasingly popular.

For a deeper look at how the underlying API choice shapes everything downstream, read my GraphQL vs REST breakdown. The short version: REST is still the right default for most startups, and TanStack Query is the right client for REST.

How React Server Components Change the Math

RSC is the plot twist nobody asked for and everybody has to deal with. When your initial data load happens on the server, streamed into the client as serialized props, the classic question of "which data fetching library" partially evaporates. For the first paint, you do not need any of these libraries. You just await your fetch in a server component.

But the story does not end there. The moment a user clicks a filter, opens a modal, or paginates a list, you are back in client land with all the old problems. You still need caching, deduping, background refetch, mutation handling, and optimistic UI. RSC does not solve any of that. It just moves the starting line.

Abstract visualization of server and client data flow

TanStack Query has adapted the fastest. Its hydration helpers let you prefetch on the server, serialize the cache, and resume on the client without refetching. This is the pattern I recommend for most Next.js App Router apps in 2026. You get the RSC benefits for initial load and the TanStack Query benefits for everything after.

SWR has a similar fallback pattern for server rendered data. It is simpler and works well if your revalidation needs are modest. Apollo has its own hydration story but it feels awkward in the App Router world because Apollo wants to own the entire data layer and RSC fundamentally challenges that assumption.

For a deeper dive on RSC patterns and when they are worth adopting, see my post on React Server Components for startups. Short version: RSC is great for content heavy pages and overkill for app like interfaces. The data fetching library still matters for the app like parts.

Client State vs Server State: Do Not Confuse Them

One mistake I see constantly is teams using TanStack Query or SWR for data that is not server state at all. Form drafts, UI toggles, modal visibility, theme preferences, these are client state. They should not live in a query cache. They should live in component state, React context, or a dedicated client state library like Zustand or Jotai.

When you blur the line, you end up with weird bugs where closing a modal triggers a network refetch, or where theme changes invalidate unrelated queries. The mental model breaks down because you are using a tool built for one job to do another.

The clean architecture looks like this. Server state lives in TanStack Query or SWR or Apollo. Client state lives in a client state library. The two communicate through hooks, not through a shared store. If you want a deeper treatment of the client state side of this split, I wrote about the tradeoffs in my Zustand vs Jotai vs Redux Toolkit comparison.

Keeping these concerns separate is one of the highest leverage architectural decisions you can make in a React codebase. It makes testing easier, it makes debugging easier, and it keeps your query cache from turning into a dumping ground for every piece of state in the app.

The Honest Verdict: Which Should You Pick in 2026

I have dodged the question long enough. Here is my opinionated rubric for picking between TanStack Query, SWR, and Apollo Client in 2026.

Developer making architectural decisions at a whiteboard

Pick TanStack Query if: you want the safest, most flexible, most teachable option. Works with any API, integrates with RSC, has the best devtools, has first class Suspense support, and has a huge community. This is my default recommendation for 90 percent of projects, including almost every startup I work with.

Pick SWR if: you are all in on Vercel and Next.js, you want the smallest bundle, you have modest caching needs, and you value minimal API surface over power features. SWR is excellent for marketing sites, content apps, and lightweight dashboards. It is also my pick when I want to ship something in a weekend without thinking about it.

Pick Apollo Client if: you have a mature GraphQL schema, you need normalized caching across many queries, you need WebSocket subscriptions, and you have senior engineers who can invest in learning type policies and fragment colocation. Apollo is the right tool for exactly this scenario and the wrong tool for everything else.

Do not pick any of them if: you are building a purely static site, your data fetching is limited to server components, or you have fewer than a dozen queries and no mutations. Sometimes the right answer is raw fetch plus React state. Do not add a library for the sake of it.

The meta lesson is that these tools are not interchangeable. They encode different assumptions about your API, your team, and your product. Picking the wrong one will not kill your project, but it will add weeks of friction that compound over the life of the codebase. Picking the right one will feel like the library disappears and you just build features.

If you are staring at a greenfield project and trying to make this call, or if you are stuck inside Apollo and wondering if the grass is greener, I help teams make exactly these decisions every week. Book a free strategy call and we can map your specific situation to the right stack in thirty minutes.

Need help building this?

Our team has launched 50+ products for startups and ambitious brands. Let's talk about your project.

TanStack QuerySWRApollo ClientReactData Fetching

Ready to build your product?

Book a free 15-minute strategy call. No pitch, just clarity on your next steps.

Get Started