lenec ru

← все посты

SWR vs TanStack Query: где разница и что брать в 2026

17K

Раз в год кто-то приходит ко мне в личку и спрашивает: «Юля, что брать — SWR или TanStack Query». И раз в год я отвечаю одно и то же: зависит от проекта. Дальше выясняется, что человек хочет короткий ответ. Короткий ответ есть, но он скучный — «бери TanStack Query, если в проекте есть мутации, бери SWR, если только чтения». Длинный ответ полезнее.

Я работала с обеими библиотеками в проде на разных проектах. Расскажу, в чём реальные различия, а не те, что в README на главной странице.

Что у них общего

На уровне «получить данные с сервера и закешировать» обе библиотеки решают одну задачу одинаково хорошо. Оба:

  • Кешируют ответы по ключу.
  • Делают stale-while-revalidate: показывают кеш, в фоне догружают свежее.
  • Дедуплицируют запросы.
  • Перезагружают данные при возврате на вкладку и при переключении фокуса.
  • Работают с пагинацией и бесконечной прокруткой.

Если у тебя проект, где данные только читаются (блог, документация, лента), — обе подходят. Разница начинается на мутациях, оптимистичных апдейтах и кеш-менеджменте.

Размер и API

SWR — около 4 КБ minified+gzipped. TanStack Query — около 13 КБ. Разница есть, но в 2026 редко бывает критической. На LCP-критичных страницах с маленьким бандлом SWR помогает, на больших приложениях лишние 9 КБ незаметны.

API SWR минималистичный:

import useSWR from "swr";

const { data, error, isLoading, mutate } = useSWR(
  "/api/user",
  fetcher,
);

API TanStack Query более структурный:

import { useQuery } from "@tanstack/react-query";

const { data, error, isLoading } = useQuery({
  queryKey: ["user"],
  queryFn: () => fetch("/api/user").then((r) => r.json()),
  staleTime: 60_000,
});

На SWR быстрее писать первые экраны. На TanStack Query больше явных параметров, и дольше пишешь, но потом проще поддерживать.

Различие №1: ключи запросов

В SWR ключ — это строка или массив, который ты руками склеиваешь. В TanStack Query ключ — массив с типизированной структурой, удобной для иерархических инвалидаций:

// TanStack Query
useQuery({ queryKey: ["posts", { author: "yulia" }] });
queryClient.invalidateQueries({ queryKey: ["posts"] }); // инвалидирует все queryKey, начинающиеся с ["posts"]

В SWR пришлось бы писать руками регулярку или итерироваться по кешу. На больших проектах с группами связанных запросов это превращается в боль. У меня на одном проекте поверх SWR появилась обёртка, которая фактически переписывала эту логику. Это был сигнал, что SWR здесь не подходит.

Различие №2: мутации

В SWR мутаций как отдельной концепции нет. Ты делаешь запрос напрямую, потом вручную дёргаешь mutate для обновления кеша:

await fetch("/api/posts", { method: "POST", body: JSON.stringify(data) });
mutate("/api/posts");

Для оптимистичных апдейтов есть useSWRMutation, но API менее удобный, чем у конкурента.

В TanStack Query мутация — отдельный хук с лайфциклом:

const mutation = useMutation({
  mutationFn: (data: PostInput) => api.createPost(data),
  onMutate: async (newPost) => {
    await qc.cancelQueries({ queryKey: ["posts"] });
    const prev = qc.getQueryData(["posts"]);
    qc.setQueryData(["posts"], (old) => [...(old ?? []), newPost]);
    return { prev };
  },
  onError: (_err, _newPost, ctx) => {
    if (ctx?.prev) qc.setQueryData(["posts"], ctx.prev);
  },
  onSettled: () => {
    qc.invalidateQueries({ queryKey: ["posts"] });
  },
});

Поэтапные хуки onMutate, onError, onSettled с возможностью передать контекст — на серьёзных проектах это экономит часы дебага. Можно вкорячить тот же сценарий и в SWR, но в TanStack Query это первоклассная фича.

Различие №3: DevTools

У TanStack Query DevTools на голову лучше. Видно весь кеш, статусы запросов, можно вручную инвалидировать или менять данные. На SWR DevTools тоже есть, но беднее.

На проектах, где много запросов и сложные взаимосвязи, DevTools TanStack Query сэкономили мне столько времени, что одного этого хватило бы, чтобы их выбрать.

Различие №4: интеграция с серверным рендером

Обе работают с SSR/RSC, но по-разному.

В Next.js App Router у TanStack Query есть прямая поддержка через HydrationBoundary: на сервере префетчишь данные, сериализуешь состояние клиента, на клиенте оно дегидрируется без повторного запроса.

// app/posts/page.tsx
import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query";

export default async function PostsPage() {
  const qc = new QueryClient();
  await qc.prefetchQuery({
    queryKey: ["posts"],
    queryFn: () => api.getPosts(),
  });

  return (
    <HydrationBoundary state={dehydrate(qc)}>
      <PostsList />
    </HydrationBoundary>
  );
}

На SWR такой готовой интеграции нет. Можно передать fallback через провайдер, но это менее структурно.

Различие №5: суспенс-режим

В TanStack Query есть отдельный хук useSuspenseQuery, который интегрируется с React Suspense. На фронте, где ты активно используешь Suspense-границы, это удобно: лоадер не пишешь руками в каждом компоненте, делегируешь его выше.

SWR тоже умеет в suspense-режим через флаг, но мне с ним было сложнее — больше странных поведений с гидрацией и SSR.

Когда я выбираю SWR

  • Маленький проект, где основная задача — показать данные.
  • Лендинг или сайт-визитка с парой динамических кусков.
  • Embed-виджет, где важна каждая килобайта бандла.
  • Команда, которая впервые работает с server-state библиотекой и не готова осваивать сложный API.

Когда я выбираю TanStack Query

  • Любой дашборд или админка, где есть мутации.
  • Проект с оптимистичными апдейтами и сложной инвалидацией.
  • Приложение с offline-сценариями (TanStack Query умеет персистить кеш и приостанавливать мутации).
  • Команда, которая будет долго поддерживать проект и хочет предсказуемой структуры.

Что не делаю никогда

Не использую обе библиотеки в одном проекте. Видела такое — половина команды на SWR, половина на TanStack Query. В итоге кэши разные, инвалидация рассогласована, после мутации одна часть UI обновляется, другая нет. Не повторяй.

Не использую ни одну из них для глобального UI-стейта. Эти библиотеки про серверные данные. UI-стейт — отдельная история (об этом есть отдельная статья).

Не пытаюсь хранить в кеше выведенные данные (например, отфильтрованный список). Кеш — про сырые ответы сервера. Производные данные считай в рендере или через useMemo.

Замер на реальном проекте

На одном из переездов с SWR на TanStack Query (около 30 запросов и 15 мутаций) у нас:

  • Бандл вырос на 9 КБ gzipped.
  • Количество багов с инвалидацией кеша снизилось примерно вдвое за квартал.
  • Время на ревью PR с мутациями стало на 20–30% меньше — структура кода стала очевиднее.

Я не сторонник чистых цифр без контекста, но в этом случае выигрыш был ощутимый. Хотя признаю: на проекте с пятью простыми запросами этот переезд был бы потерянным временем.

Что запомнить

SWR и TanStack Query решают одну задачу с разной плотностью API. Для маленьких проектов SWR быстрее в старте и легче в бандле. Для всего, где есть мутации и сложная инвалидация, TanStack Query сэкономит часы на отладке и принесёт DevTools, ради которых стоило бы взять её даже без других плюсов.

Не выбирай по моде, выбирай по тому, какие сценарии у тебя реально есть в проекте. Если основная задача — показать данные, бери что попроще. Если задача — управлять данными, бери что предсказуемее.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.