TanStack Query vs. SWR: Simplicity vs. Control in Modern React
Compare TanStack Query and SWR for React and Next.js. Explore code examples, mutations, bundle size, and find the right data-fetching tool for your stack.
By Muhammad Huzaifa, Full-Stack Software Engineer

React Query vs. useSWR
When building scalable frontend applications in React and Next.js, managing server state inside useEffect quickly turns into an anti-pattern. Both TanStack Query (React Query) and Vercel's SWR solve this by automating background revalidation, cache invalidation, and request deduplication.
However, each library targets a distinct engineering tradeoff: SWR prioritizes minimal overhead and rapid velocity, while TanStack Query prioritizes fine-grained cache orchestration.
Basic Data Fetching: SWR vs. TanStack Query
SWR adopts a lightweight, URL-as-key paradigm:
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then((res) => res.json());
function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useSWR(`/api/users/${userId}`, fetcher);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Failed to load user.</p>;
return <h1>{data.name}</h1>;
}TanStack Query uses explicit structured array keys and a configuration object, giving you granular control over cache lifecycles.
import { useQuery } from '@tanstack/react-query';
const fetchUser = async (id: string) => {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error('Network error');
return res.json();
};
function UserProfile({ userId }: { userId: string }) {
const { data, error, isPending } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
});
if (isPending) return <p>Loading...</p>;
if (error) return <p>Failed to load user.</p>;
return <h1>{data.name}</h1>;
}Mutations and Cache Invalidation
SWR keeps mutations straightforward using its scoped mutate helper:
import useSWR, { useSWRConfig } from 'swr';
function UpdateName() {
const { mutate } = useSWRConfig();
const handleUpdate = async (newName: string) => {
// Optimistic UI update, then trigger background revalidation
mutate('/api/user', { name: newName }, false);
await fetch('/api/user', {
method: 'POST',
body: JSON.stringify({ name: newName }),
});
mutate('/api/user');
};
return <button onClick={() => handleUpdate('Huzaifa')}>Update Name</button>;
}TanStack Query offers a dedicated useMutation hook with complete lifecycle handlers (onMutate, onError, onSettled) for fine-grained rollbacks:
import { useMutation, useQueryClient } from '@tanstack/react-query';
function UpdateName() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newName: string) =>
fetch('/api/user', {
method: 'POST',
body: JSON.stringify({ name: newName }),
}),
onSuccess: () => {
// Invalidate all queries starting with the 'users' key
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
return (
<button onClick={() => mutation.mutate('Huzaifa')}>Update Name</button>
);
}Key Architectural Differences
- Bundle Footprint: SWR weighs ~4.5 kB, making it ideal for performance-sensitive client bundles. TanStack Query sits around ~13 kB due to its extensive feature surface.
- Cache Organization: SWR works naturally with simple endpoints, whereas TanStack Query's array-based keys (
['users', id, 'posts']) simplify invalidating related sub-trees of data. - Developer Experience: TanStack Query includes dedicated, official DevTools for inspecting queries and cache state in real time. SWR relies primarily on browser logging or third-party extensions.
- Cross-Framework Support: SWR is tailored strictly for React/Next.js environments, while TanStack Query is framework-agnostic (React, Vue, Solid, Svelte).
Summary & Recommendation
In my engineering experience:
- Choose SWR if you are building small-to-medium Next.js applications, want zero-friction caching, and prefer minimal cognitive overhead for standard REST/fetch workflows.
- Choose TanStack Query if your application demands complex optimistic updates, deep cache invalidation hierarchies, offline synchronization, or multi-framework consistency.