Web razvoj
Next.jsReact QuerySWRApp RouterRSCCacheiranjePerformanse

React Query vs SWR u Next.js App Routeru: Kada koristiti koji (i kako izbjeći dvostruko dohvaćanje)

AO
Adrijan Omićević
·13 min čitanja

# Trenutno stanje#

Next.js App Router promijenio je način na koji timovi razmišljaju o dohvaćanju podataka: React Server Components se izvršavaju na serveru, Client Components u pregledniku, a Next.js fetch dodaje vlastito cacheiranje i deduplikaciju povrh svega. To stvara čest problem: renderirate podatke na serveru, a zatim vaša biblioteka za client cache ponovno dohvaća iste podatke nakon hydracije.

Ovaj post uspoređuje React Query vs SWR u Next.js App Routeru s fokusom na stvarne produkcijske potrebe: ponašanje cachea, SSR i RSC kompatibilnost, mutacije, optimistična ažuriranja, developer experience te konkretne obrasce za izbjegavanje dvostrukog dohvaćanja.

ℹ️ Napomena: Ova usporedba pretpostavlja Next.js App Router s uključenim React Server Components te moderne verzije obje biblioteke. Za dublje osnove RSC-a pročitajte vodič za React Server Components. Za nijanse i kompromise oko Next.js cacheiranja pogledajte Next.js caching strategies SSR ISR SWR.

# Brza tablica usporedbe#

KriterijReact Query (TanStack Query)SWR (Vercel)
Primarni modelQuery cache s eksplicitnim ključevima i invalidacijomStale-while-revalidate cache po ključu s revalidacijom
Najbolji zaSloženi server state, dashboarde, SaaS CRUD, optimistična ažuriranjaJednostavno read-heavy dohvaćanje, manje aplikacije, content widgete
MutacijePrvorazredni useMutation, retryji, invalidacijski pipelineovimutate i helperi za mutacije, više ručne orkestracije
Optimistična ažuriranjaSnažni, dobro dokumentirani obrasciMoguća, ali obično uz više custom logike
RSC kompatibilnostKoristi se u Client Components, hidrira sa serveraKoristi se u Client Components, daje fallback podatke
Izbjegavanje dvostrukog dohvaćanjaPrefetch na serveru i dehydrate ili proslijedi initialDataProslijedi fallback kroz provider ili izbjegni server fetch za isti ključ
DevtoolsOdličanMinimalan
EkosustavOgroman, multi-framework, snažni obrasciLean, manja površina API-ja
Krivulja učenjaSrednjaNiska
Utjecaj na bundle sizeVećiManji

# Kako cache stvarno radi u App Routeru#

Zbunjenost u App Routeru dolazi iz toga što je u igri više cache slojeva:

  1. 1
    Next.js fetch cache na serveru: cacheira po requestu ili po route segmentu, ovisno o cache i next.revalidate.
  2. 2
    RSC render deduping: ponovljeni fetch pozivi s istim inputom mogu se deduplicirati tijekom rendera.
  3. 3
    Client cache: React Query ili SWR cacheiraju podatke u pregledniku kroz rendere, navigacije i refocus evente.

Dvostruko dohvaćanje se događa kada koristite i server fetch i client fetch za isti resurs, bez hidracije client cachea.

Scenarij dvostrukog dohvaćanja u jednoj rečenici#

Dohvatite na serveru za inicijalni HTML, a zatim se Client Component mounta i ponovno dohvaća jer je njegov cache prazan.

To nije samo potrošen bandwidth. Na sporijim mrežama može dodati 100 ms do 500 ms dodatne latencije i uzrokovati “flicker” UI-ja kada se podaci promijene između server rendera i client revalidacije. Za osnove performansi i profiliranje pogledajte website performance optimization.

# Modeli cacheiranja: React Query vs SWR#

React Query model cachea#

React Query tretira server state kao normalizirani cache indeksiran po query ključevima. Svježinu kontrolirate pomoću staleTime, a ponašanje refetcha na focus, reconnect i mount. Također dobivate eksplicitnu invalidaciju i ponovno dohvaćanje preko queryClient.invalidateQueries.

Praktične implikacije:

  • Ako vaš dashboard ima 20 widgeta i dijele upite, React Query daje predvidljivu deduplikaciju i invalidaciju.
  • Ako ažurirate jedan entitet, možete invalidirati povezane upite po djelomičnim ključevima i pustiti React Query da refetcha samo ono što je “stale”.

SWR model cachea#

SWR implementira stale-while-revalidate: odmah vraća cacheirane podatke, a zatim u pozadini radi revalidaciju. Namjerno je minimalističan: ključ, fetcher i kontrole revalidacije. Odličan je kada želite brzo, jednostavno client cacheiranje uz malo konfiguracije.

Praktične implikacije:

  • Super za jednostavne stranice i male client widgete koji se mogu oportunistički revalidirati.
  • Za složene grafove invalidacije obično završite tako da ručno pozivate mutate preko više ključeva ili pišete pomoćne utilse.

🎯 Ključni zaključak: React Query daje jače primitive za velike grafove upita i invalidaciju; SWR ostaje jednostavniji, ali s rastom kompleksnosti više orkestracije prebacuje na vašu aplikaciju.

# SSR i RSC kompatibilnost u Next.js App Routeru#

Ni React Query ni SWR ne bi se trebali koristiti izravno unutar Server Components. App Router očekuje da dohvaćate na serveru koristeći fetch ili server actions, pa zatim podatke proslijedite u Client Components.

Preporučeni mentalni model#

  • Server Components: dohvat s Next.js fetch i cacheiranjem route segmenata.
  • Client Components: React Query ili SWR za interaktivnost, pozadinski refetch, paginaciju, mutacije i optimistična ažuriranja.
  • Izbjegavanje duplog dohvaćanja: hidrirajte client cache s podacima dohvaćenim na serveru.

React Query SSR hydration obrazac za App Router#

Najpouzdaniji pristup je: prefetch na serveru, dehydrate, pa hydrate u Client Provideru. Držite kod minimalnim i konzistentnim.

TSX
// app/providers.tsx
"use client";
 
import { QueryClient, QueryClientProvider, HydrationBoundary } from "@tanstack/react-query";
import { useState } from "react";
 
export function Providers(props: { state: unknown; children: React.ReactNode }) {
  const [client] = useState(() => new QueryClient());
  return (
    <QueryClientProvider client={client}>
      <HydrationBoundary state={props.state}>{props.children}</HydrationBoundary>
    </QueryClientProvider>
  );
}
TSX
// app/page.tsx (Server Component)
import { dehydrate, QueryClient } from "@tanstack/react-query";
import { Providers } from "./providers";
 
async function getUser() {
  const res = await fetch("https://api.example.com/me", { cache: "no-store" });
  if (!res.ok) throw new Error("Failed");
  return res.json();
}
 
export default async function Page() {
  const qc = new QueryClient();
  await qc.prefetchQuery({ queryKey: ["me"], queryFn: getUser });
  const state = dehydrate(qc);
 
  return (
    <Providers state={state}>
      {/* Client component uses useQuery(["me"]) without refetching */}
    </Providers>
  );
}

Ključne točke:

  • Vaš queryFn treba pozivati fetch s ispravnom cache semantikom.
  • Preferirajte cache: "no-store" za user-specific podatke ili strategiju revalidacije za javne podatke.
  • Postavite staleTime u client upitu kako biste izbjegli trenutni refetch na mountu.

SWR fallback obrazac za App Router#

SWR nudi fallback za seedanje cachea. Obrazac je sličan: dohvatite na serveru, proslijedite kao fallback, zatim koristite useSWR u Client Components s istim ključem.

TSX
// app/swr-provider.tsx
"use client";
 
import { SWRConfig } from "swr";
 
export function SWRProvider(props: { fallback: Record<string, unknown>; children: React.ReactNode }) {
  return <SWRConfig value={{ fallback: props.fallback }}>{props.children}</SWRConfig>;
}
TSX
// app/page.tsx (Server Component)
import { SWRProvider } from "./swr-provider";
 
async function getUser() {
  const res = await fetch("https://api.example.com/me", { cache: "no-store" });
  if (!res.ok) throw new Error("Failed");
  return res.json();
}
 
export default async function Page() {
  const user = await getUser();
 
  return (
    <SWRProvider fallback={{ "/api/me": user }}>
      {/* Client component uses useSWR("/api/me") without refetching */}
    </SWRProvider>
  );
}

Ključne točke:

  • SWR ključ mora se točno podudarati, uključujući query string i konvencije base patha.
  • Ako vaš client fetcher gađa drugi URL od server fetcha, i dalje ćete dobiti dvostruko dohvaćanje.

⚠️ Upozorenje: Najčešći double-fetch bug su nepodudarni ključevi. Server fetch na https://api.example.com/me i client fetch na /api/me su različiti cachevi i oba će se izvršiti ako ne ujednačite izvor ili ne seedate oba.

# Mutacije i optimistična ažuriranja#

Mutacije su mjesto gdje razlika postaje najvidljivija u stvarnim aplikacijama.

React Query mutacije#

React Query ima namjenski API za mutacije s jasnim lifecycleom:

  • onMutate za optimistična ažuriranja
  • onError za rollback
  • onSuccess za invalidaciju ili ažuriranje upita
  • onSettled za cleanup

Ovo je idealno za SaaS aplikacije s puno CRUD-a gdje je “perceived speed” važan. Česta produkt metrika: čak i 100 ms do 300 ms percipiranog poboljšanja tijekom učestalih akcija može smanjiti churn u internim alatima, jer UI djeluje responsivno.

Primjer obrasca optimističnog ažuriranja:

TSX
"use client";
 
import { useMutation, useQueryClient } from "@tanstack/react-query";
 
export function useUpdateProfile() {
  const qc = useQueryClient();
 
  return useMutation({
    mutationFn: async (payload: { name: string }) => {
      const res = await fetch("/api/profile", {
        method: "PATCH",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (!res.ok) throw new Error("Failed");
      return res.json();
    },
    onMutate: async (payload) => {
      await qc.cancelQueries({ queryKey: ["me"] });
      const prev = qc.getQueryData(["me"]);
      qc.setQueryData(["me"], (old: any) => ({ ...old, name: payload.name }));
      return { prev };
    },
    onError: (_err, _payload, ctx) => {
      qc.setQueryData(["me"], ctx?.prev);
    },
    onSettled: () => {
      qc.invalidateQueries({ queryKey: ["me"] });
    },
  });
}

SWR mutacije#

SWR podržava mutacije preko mutate i helpera za mutacije, i možete raditi optimistična ažuriranja. Razlika je u tome što često pišete više “glue” koda za rollback i za ažuriranje više ključeva.

Čest pristup je:

  • mutate(key, updater, false) za lokalno ažuriranje bez revalidacije
  • izvršiti request
  • revalidirati ili rollbackati

To je u redu za nekoliko endpointa. Postaje rizično kad imate više ovisnih cacheva, list i detail viewove te paginaciju.

# Developer experience i timski workflow#

React Query DX#

React Query obično pobjeđuje kada codebase naraste:

  • Konvencije za query ključeve dobro skaliraju kroz timove.
  • Devtools čine stanje cachea i razloge refetcha očitima.
  • Obrasci invalidacije su konzistentni, što smanjuje bugove tijekom razvoja featurea.

Kompromis je učenje mentalnog modela: staleTime vs cacheTime, semantika invalidacije i hydration boundaryji.

SWR DX#

SWR često pobjeđuje po brzini isporuke:

  • Vrlo malo setupa.
  • Minimalno koncepata.
  • Lako ga je “posuti” po malim Client Components.

Kompromis je što ćete sami izgraditi konvencije za ključeve, politike revalidacije i obrasce mutacija. Bez jakih konvencija, veći timovi završavaju s nekonzistentnim cache ponašanjem kroz aplikaciju.

💡 Savjet: Ako odaberete SWR za rastuću aplikaciju, rano definirajte jedan utility za izgradnju ključeva. Neka jedno mjesto slaže ključeve za list, detail i filtrirane varijante. To sprječava 80 posto slučajnih duplih ključeva i kasnijih invalidacija.

# Izbjegavanje dvostrukog dohvaćanja: provjereni obrasci#

Dvostruko dohvaćanje rijetko je bug biblioteke. Gotovo uvijek je arhitekturni nesrazmjer.

Obrazac A: Server fetch za first paint, hidracija client cachea#

Koristite kada:

  • SEO ili brz first paint je bitan.
  • Isti podaci se odmah koriste u Client Componentu.
  • Želite interaktivnost nakon hydracije bez refetcha.

React Query: prefetchQuery + dehydrate + HydrationBoundary.

SWR: server fetch + SWRConfig fallback.

Obrazac B: Isključivo client fetch, bez server fetcha za te podatke#

Koristite kada:

  • Podaci su user-specific i nisu potrebni za inicijalni HTML.
  • Želite izbjeći server load i zadržati rutu statičnom.
  • Možete prikazati skeleton ili placeholder.

Ovo je često za dashboarde koji brzo renderiraju okvir, a zatim postupno učitavaju widgete.

Obrazac C: Neka Next.js cache upravlja javnim sadržajem, client cache koristite štedljivo#

Za content stranice:

  • Dohvaćajte sadržaj u Server Components koristeći next.revalidate.
  • Izbjegavajte client caching biblioteke osim ako imate interaktivne widgete kojima treba client-side revalidacija.

Time smanjujete bundle size i posao oko hydracije. Također se bolje uklapa u prednosti App Routera.

Debug checklist za duple zahtjeve#

  1. 1
    Potvrdite je li prvi request server-side, a drugi client-side.
  2. 2
    Provjerite jednakost ključeva: identičan SWR key string ili React Query queryKey array.
  3. 3
    Provjerite jednakost URL-ova: isti path i query string, iste pretpostavke o base URL-u.
  4. 4
    Postavite staleTime u React Queryju da spriječite trenutni refetch nakon hydracije.
  5. 5
    U SWR-u isključite revalidateOnMount kada stvarno želite vjerovati fallbacku neko vrijeme.

# Preporuke prema tipu aplikacije#

Dashboardi i interni alati#

Tipične značajke: puno widgeta, filteri, paginacija, česte mutacije i podaci koji se često mijenjaju.

Preporuka:

  • Preferirajte React Query zbog predvidljivih query grafova, deduplikacije i invalidacije.
  • Server rendering koristite za layout i samo kritične sažetke, a zatim hidrirajte widgete ili ih učitavajte client-only.

Zašto je važno:

  • Dashboardi često pokreću desetke requestova. React Query cache sprječava ponovljene refetcheve kada korisnici navigiraju između tabova ili otvaraju detail drawerove.

SaaS proizvodi s CRUD-om i “real-time-ish” UX-om#

Tipične značajke: liste i detail viewovi, forme, optimistična ažuriranja, višekoračni flowovi i visoka UX očekivanja.

Preporuka:

  • Preferirajte React Query za mutacije i optimistična ažuriranja.
  • invalidateQueries koristite strateški umjesto globalnog refetcha.
  • Razmislite o kombinaciji server actions za write operacije i React Queryja za ažuriranje client cachea.

Zašto je važno:

  • Optimistična ažuriranja smanjuju percipiranu latenciju i čine proizvod bržim. React Query daje sigurnije obrasce za rollback.

Content stranice i marketing#

Tipične značajke: većinom read-only sadržaj, SEO-driven, relativno stabilni podaci, malo client interakcije.

Preporuka:

  • Preferirajte SWR samo za male client widgete poput provjere statusa newslettera, dostupnosti cijena ili personalization flagova.
  • Za glavni sadržaj koristite Server Components i Next.js caching i u mnogim slučajevima preskočite client caching biblioteke.

Zašto je važno:

  • Na content stranicama bundle size i vrijeme hydracije važniji su od sofisticiranog client cacheiranja.

# Savjeti za migraciju: SWR → React Query i React Query → SWR#

Migracija je manje zamjena hookova, a više usvajanje modela cacheiranja.

Migracija sa SWR-a na React Query#

Što se mijenja:

  • Ključevi idu od proizvoljnih stringova prema strukturiranim query ključevima, obično arrayevima.
  • Revalidacija postaje invalidacija i refetching, što je eksplicitnije.
  • Mutacije postaju useMutation s lifecycle handlerima.

Praktični koraci:

  1. 1
    Kreirajte konvenciju query ključeva, npr. ["users", "list", filters] i ["users", "detail", id].
  2. 2
    Wrapajte aplikaciju u jedan QueryClientProvider u app/providers.tsx.
  3. 3
    Prvo zamijenite kritične SWR hookove, počevši od endpointa koji uzrokuju najviše dvostrukog dohvaćanja ili nekonzistentnih ažuriranja.
  4. 4
    Zamijenite SWR mutate pozive s useMutation + invalidateQueries.

Predloženo mapiranje:

KonceptSWRReact Query
Hook za čitanjeuseSWR(key, fetcher)useQuery({ queryKey, queryFn })
Globalno ažuriranje cacheamutate(key)queryClient.invalidateQueries({ queryKey })
Optimistična ažuriranjamutate(key, data, false)onMutate + setQueryData
Prefill sa serveraSWRConfig fallbackdehydrate + HydrationBoundary

Migracija s React Queryja na SWR#

To se obično događa kada:

  • Aplikacija je read-heavy i želite manje apstrakcije.
  • Želite smanjiti bundle size i mentalni overhead.
  • Rijetko imate mutacije ili ih možete izolirano riješiti.

Praktični koraci:

  1. 1
    Auditirajte korištenje query ključeva i pojednostavite u stabilne string ključeve.
  2. 2
    Zamijenite invalidacijske obrasce s eksplicitnim mutate pozivima na relevantnim ključevima.
  3. 3
    Ponovno procijenite stale politike: SWR defaulti mogu revalidirati češće nego što očekujete.
  4. 4
    Ako ste se oslanjali na Devtools za debugiranje, dodajte lagani logging oko fetchera i mutation flowova.

⚠️ Upozorenje: Ako vaša aplikacija ovisi o složenoj invalidaciji, prelazak s React Queryja na SWR može povećati rizik od bugova. Timovi često promaše jedan od povezanih ključeva, pa UI ostane stale u edge flowovima poput bulk edita ili paginacije.

# Cijena, održavanje i uklapanje u ekosustav#

Obje biblioteke su zrele i široko korištene.

  • SWR održava Vercel i dobro se uklapa u Next.js ekosustav.
  • React Query je dio TanStacka i široko se koristi u Reactu i šire.

Iz perspektive rizika, obje su siguran izbor. Odlučujući faktori su kompleksnost aplikacije i koliko želite da biblioteka upravlja za vas.

# Ključni zaključci#

  • Izbjegnite dvostruko dohvaćanje tako da dohvatite jednom i hidrirate client cache: React Query koristi dehydrate i HydrationBoundary, SWR koristi SWRConfig fallback.
  • Odaberite React Query za dashboarde i SaaS aplikacije s čestim mutacijama, složenom invalidacijom i optimističnim ažuriranjima.
  • Odaberite SWR za jednostavno read-heavy client dohvaćanje ili male interaktivne widgete unutar content-first Next.js stranica.
  • Ujednačite cache ključeve i URL-ove između servera i klijenta, inače hidracija neće spriječiti duple requestove.
  • U App Routeru tretirajte Server Components i Next.js fetch caching kao primarni mehanizam za inicijalne podatke, a client caching biblioteke koristite za interaktivnost nakon hydracije.

# Zaključak#

React Query i SWR oba rade u Next.js App Routeru, ali optimizirani su za različite stvarnosti. Ako je vaša aplikacija “mutation-heavy” ili ima složen query graf, React Query će uštedjeti inženjersko vrijeme i smanjiti bugove sa stale UI-jem. Ako su potrebe većinom read-only i želite najjednostavniji alat koji radi, SWR je često brži put.

Ako želite pomoć oko odabira pristupa, rješavanja dvostrukog dohvaćanja ili postavljanja skalabilne caching strategije za App Router, kontaktirajte Samioda i pregledat ćemo vaš trenutni data flow te predložiti konkretan plan implementacije.

FAQ

Share
A
Adrijan OmićevićSamioda Team
All articles →

Trebate pomoć s projektom?

Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.