Baik React Query (kini TanStack Query) maupun SWR memecahkan masalah yang sama: mengelola remote state di React tanpa prop drilling dan spaghetti useEffect yang dihasilkan dari panggilan fetch manual. Saya telah menggunakan keduanya di proyek produksi. SWR menggerakkan situs Next.js publik yang lebih sederhana; React Query menjalankan dashboard untuk alat ERP internal dengan mutasi kompleks, pembaruan optimis, dan dukungan offline.
| Dimensi | SWR | React Query |
|---|---|---|
| Ukuran bundle | Sekitar 4KB minified dan gzipped | Sekitar 13KB minified dan gzipped |
| Mutations | Panggilan mutate() manual, tanpa API mutation bawaan | Hook useMutation khusus dengan optimistic update |
| Model caching | Cache stale-while-revalidate yang sederhana | Cache yang bisa dikonfigurasi lewat query key, staleTime, dan gcTime |
| Dukungan offline | Revalidate-on-reconnect dasar | Antrean offline dan retry mutation bawaan |
| DevTools | Tidak ada panel devtools resmi | Devtools khusus untuk memeriksa cache dan state query |
| Paling cocok untuk | Data fetching sederhana di halaman publik | Dashboard kompleks dengan mutation dan kebutuhan offline |
Perbedaan Inti: Filosofi
SWR (Stale-While-Revalidate) adalah dari Vercel dan dioptimalkan untuk ekosistem Vercel/Next.js. Filosofinya adalah surface API minimal — satu hook useSWR mencakup sebagian besar kasus penggunaan. React Query dari TanStack lebih beropini tentang manajemen server state, dilengkapi dengan DevTools, dan memiliki dukungan kelas pertama untuk mutasi, pagination, infinite query, dan mode offline.
Apa yang Mereka Bagikan
Kedua library mendeduplikasi request — jika tiga komponen di halaman yang sama memanggil useSWR('/api/user') atau useQuery(['user']), hanya satu request HTTP yang dikirim. Keduanya melakukan revalidasi saat window fokus dan reconnect. Keduanya mendukung background refetching, fungsi fetcher kustom, dan konfigurasi global.
Kapan SWR Menang
SWR menang untuk situs berat konten di mana kamu terutama perlu mengambil dan menampilkan data dengan mutasi sederhana sesekali. API-nya lebih kecil untuk dipelajari, terintegrasi sempurna dengan ekosistem Vercel, dan untuk CRUD sederhana tanpa kebutuhan invalidasi cache yang kompleks, ergonomisnya lebih bersih.
Feature Comparison: React Query vs SWR
┌─────────────────────────┬──────────────┬──────────────┐
│ Feature │ React Query │ SWR │
├─────────────────────────┼──────────────┼──────────────┤
│ Bundle size (min+gzip) │ ~13.5 kB │ ~4.2 kB │
│ Mutations │ ✅ Built-in │ ⚠️ Manual │
│ Optimistic updates │ ✅ Easy │ ✅ Possible │
│ Infinite scroll │ ✅ Built-in │ ✅ Built-in │
│ Offline support │ ✅ Yes │ ⚠️ Partial │
│ DevTools │ ✅ Official │ ❌ None │
│ Server State (SSR) │ ✅ Full │ ✅ Full │
│ Background refetch │ ✅ Yes │ ✅ Yes │
│ Request deduplication │ ✅ Yes │ ✅ Yes │
│ Prefetching │ ✅ Yes │ ⚠️ Manual │
│ Learning curve │ Medium │ Low │
│ Best for │ Complex apps │ Simple fetch │
└─────────────────────────┴──────────────┴──────────────┘Di Next.js App Router, prefetch data sisi server menggunakan QueryClient.prefetchQuery() di Server Component-mu, kemudian teruskan state yang di-dehydrate ke HydrationBoundary di Client Component. Ini memberimu zero-loading-state pada render pertama sambil tetap mendapatkan semua manajemen cache sisi klien React Query.
Mutasi: Di Mana React Query Unggul
Mutasi SWR memerlukan pembaruan cache manual melalui fungsi mutate() — kamu memberi tahu SWR apa nilai cache baru seharusnya. Hook useMutation React Query memiliki siklus hidup onMutate / onError / onSettled yang membuat pembaruan optimis, rollback pada error, dan invalidasi cache menjadi pola terstruktur daripada logika ad-hoc.
Infinite Query dan Pagination
Kedua library mendukung infinite scrolling dengan useInfiniteQuery (React Query) dan useSWRInfinite (SWR). Implementasi React Query lebih ergonomis — array pages dan fungsi fetchNextPage() menangani sebagian besar pola pagination tanpa logika kustom. SWR memerlukan pengkabelan manual lebih banyak tapi memberimu lebih banyak kontrol atas strategi penggabungan data.
// ─── React Query: full mutation with optimistic update ───
import { useMutation, useQueryClient } from "@tanstack/react-query"
function useUpdateProduct() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: UpdateProductDto) =>
fetch(`/api/products/${data.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}).then((r) => r.json()),
// Optimistic update
onMutate: async (newProduct) => {
await queryClient.cancelQueries({ queryKey: ["products", newProduct.id] })
const prev = queryClient.getQueryData(["products", newProduct.id])
queryClient.setQueryData(["products", newProduct.id], newProduct)
return { prev }
},
onError: (_err, newProduct, ctx) => {
queryClient.setQueryData(["products", newProduct.id], ctx?.prev)
},
onSettled: (_data, _err, vars) => {
queryClient.invalidateQueries({ queryKey: ["products", vars.id] })
queryClient.invalidateQueries({ queryKey: ["products"] })
},
})
}
// ─── SWR: fetching + manual mutation ───
import useSWR, { mutate } from "swr"
const fetcher = (url: string) => fetch(url).then((r) => r.json())
function useProducts() {
const { data, error, isLoading } = useSWR("/api/products", fetcher, {
revalidateOnFocus: true,
dedupingInterval: 5000,
})
const updateProduct = async (id: string, payload: Partial<Product>) => {
// Optimistic update with SWR
await mutate(
"/api/products",
async (current: Product[]) => {
const updated = await fetch(`/api/products/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}).then((r) => r.json())
return current.map((p) => (p.id === id ? updated : p))
},
{ optimisticData: (current) =>
current?.map((p) => (p.id === id ? { ...p, ...payload } : p)) }
)
}
return { data, error, isLoading, updateProduct }
}Server State vs Client State
Wawasan kunci yang dibagikan kedua library: server state secara fundamental berbeda dari client state. Client state (toggle UI, input form, modal buka/tutup) milik useState atau state manager seperti Zustand. Server state (record database, respons API) milik React Query atau SWR. Mencampurnya menyebabkan data basi, race condition, dan bug sinkronisasi.
Menggunakan kedua library dalam codebase yang sama menggandakan biaya bundle dan menciptakan dua layer cache terpisah yang tidak saling mengetahui. Pilih satu saat awal proyek dan pertahankan. Jika kamu mewarisi codebase dengan keduanya, migrasikan semuanya ke React Query (lebih capable) dan hapus SWR.
Mengintegrasikan dengan Next.js App Router
Di Next.js App Router, Server Component menangani pengambilan awal — tidak perlu React Query atau SWR untuk first paint. React Query dan SWR masuk untuk interaksi sisi klien setelah hidrasi: merefresh data pada aksi pengguna, polling untuk pembaruan langsung, dan mutasi. Bungkus Client Component-mu dalam QueryClientProvider atau SWRConfig di root layout-mu.
Rekomendasi Saya
Untuk proyek Next.js baru: mulai dengan SWR. Ini mencakup 80% kasus penggunaan dengan bundle lebih kecil dan model mental yang lebih sederhana. Migrasi ke React Query jika kamu membutuhkan DevTools untuk men-debug cache state, melakukan mutasi optimis yang memerlukan rollback, memiliki dependent query yang kompleks, atau membutuhkan perilaku offline-first. Untuk dashboard ERP, panel admin, dan aplikasi berat data: mulai dengan React Query.

