Web razvoj
ReactNext.jsTanStack QuerySWRUXOpservabilnostVodič

Rukovanje greškama u Reactu u 2026.: Error Boundaries, ponovni pokušaji i UX obrasci koji skaliraju

AO
Adrijan Omićević
·14 min čitanja

# Zašto rukovanje greškama u Reactu treba slojeviti sustav u 2026.#

Moderni React sustavi su kompozabilni, streaming i izrazito asinkroni. To znači da se greške mogu dogoditi u više ravnina: renderiranje, routing, mreža, dozvole i ovisnosti trećih strana.

Pristup koji se dobro skalira tretira rukovanje greškama kao slojevitu obranu, a ne kao jedan “catch-all”. Timovi koji se oslanjaju na jedan globalni toast i jedan generički fallback često isporučuju krhak UX i stvaraju slijepe točke u opservabilnosti.

Ovaj vodič se fokusira na obrasce rukovanja greškama u Reactu koji se mogu skalirati kroz timove i proizvode razdvajanjem odgovornosti:

  • Granice na razini komponente za UI kvarove
  • Granice na razini rute za izolaciju na razini stranice
  • Ponovni pokušaji dohvata podataka za prolazne mrežne probleme
  • Poruke korisnicima koje nude jasan put oporavka

Dobit ćete i ponovno upotrebljive obrasce i anti-patterns za TanStack Query i SWR, uključujući politike ponovnih pokušaja i ponašanje mutacija.

# Slojeviti model: gdje treba hvatati greške#

Pouzdan sustav odgovara na dva pitanja za svaki kvar.

  1. 1
    Gdje treba ograničiti grešku kako bi aplikacija ostala upotrebljiva
  2. 2
    Gdje korisniku treba ponuditi smislen način oporavka

Koristite ovu slojevitu mapu kao zadani okvir.

SlojHvataTipični simptomiNajbolji UI odgovor
Granica komponenteGreške renderiranja u widgetu ili značajciPokvarena kartica, graf, editorInline fallback za tu komponentu
Granica ruteGreške stranice ili segmenta rutePuca cijela stranicaStranica greške na razini rute s retry i navigacijom
Ponovni pokušaji u podatkovnom slojuProlazne mrežne greške i 5xxSpinneri zapnu, povremeni padoviAutomatski retry pa inline error stanje
Poruke korisnicimaIshodi koji presijecaju aplikacijuSpremanje nije uspjelo, sesija je isteklaToast za globalne događaje, inline za polja forme

🎯 Ključna poruka: Cilj nije spriječiti greške. Cilj je ograničiti “blast radius”, omogućiti oporavak i držati korisnike u pokretu.

Ako gradite s Next.js App Routerom, uskladite ovaj model s granicama grešaka po segmentima rute i loading stanjima. Ovo se dobro uklapa s obrascima u Next.js App Router: granice grešaka, loading i streaming obrasci.

# Component Error Boundaries koje ne uništavaju stranicu#

Error boundaries su i dalje osnovni React primitiv za izolaciju UI kvarova. U 2026. standard je postavljati granice oko feature widgeta, a ne oko cijele aplikacije.

Što Error Boundaries hvataju, a što ne#

Error boundaries hvataju:

  • Greške tijekom renderiranja
  • Greške u konstruktorima
  • Greške u lifecycle metodama class komponenti

Ne hvataju:

  • Greške u event handlerima
  • Async greške u promiseovima ili fetch
  • Greške unutar setTimeout callbackova
  • Greške na serveru osim ako ih ponovno ne bacite u render na klijentu

Zato vam treba strategija u podatkovnom sloju, ne samo granice.

Ponovno upotrebljiva Error Boundary komponenta#

Neka granica bude mala i dosljedna, a fallback UI neka korisniku nudi akciju.

TSX
import React from "react";
 
type Props = {
  name: string;
  fallback: (args: { error: Error; reset: () => void }) => React.ReactNode;
  children: React.ReactNode;
  onError?: (error: Error) => void;
};
 
type State = { error: Error | null };
 
export class ErrorBoundary extends React.Component<Props, State> {
  state: State = { error: null };
 
  static getDerivedStateFromError(error: Error) {
    return { error };
  }
 
  componentDidCatch(error: Error) {
    this.props.onError?.(error);
  }
 
  reset = () => this.setState({ error: null });
 
  render() {
    if (this.state.error) {
      return this.props.fallback({ error: this.state.error, reset: this.reset });
    }
    return this.props.children;
  }
}

Primjer korištenja za widget s grafom:

TSX
<ErrorBoundary
  name="RevenueChart"
  onError={(e) => console.error("RevenueChart failed", e)}
  fallback={({ reset }) => (
    <div>
      <p>Graf se nije uspio učitati.</p>
      <button onClick={reset}>Pokušaj ponovno</button>
    </div>
  )}
>
  <RevenueChart />
</ErrorBoundary>

Obrazac: granica po značajci, ne po stranici#

Jedna stranica često sadrži više neovisnih značajki. Ako jedna pukne, ostale trebaju ostati funkcionalne. To je posebno važno u dashboardima i admin alatima.

Praktično pravilo: jedna granica po “widgetu” koji može neovisno otkazati. Ako stranica ima 6 widgeta, vjerojatno želite 6 granica, a ne 1.

💡 Savjet: Granice nazivajte prema značajkama koje korisnik prepoznaje, a ne prema tehničkim komponentama. Logovi postaju pretraživi i korisni kad se naziv poklapa s jezikom proizvoda.

Anti-pattern: hvatanje i ignoriranje u renderu#

Izbjegavajte omatanje render logike u try/catch i vraćanje null. Time skrivate kvar od opservabilnosti i onemogućujete dosljedan recovery UI.

Ako nešto može baciti grešku tijekom renderiranja, pustite da je baci i neka je uhvati granica gdje možete prikazati fallback i logirati.

# Granice rute: zadržite kvarove na razini stranice#

Granice rute rješavaju greške koje sprječavaju da se stranica uopće renderira. U Next.js App Routeru to se tipično mapira na obradu grešaka po segmentu rute. U React Routeru koristite error elemente na razini rute.

Granice rute trebaju dati prioritet:

  • Jasnoj poruci da se stranica nije učitala
  • Akciji ponovnog pokušaja
  • Putanji natrag na sigurnu lokaciju, poput dashboarda ili početne
  • Minimalnoj buci, maksimalnoj mogućnosti oporavka

Za što služe granice rute#

Koristite granice rute za:

  • Greške autorizacije koje se dogode tijekom učitavanja rute
  • Nedostajuće kritične podatke koji blokiraju stranicu
  • Neočekivane greške renderiranja koje zahvaćaju većinu stranice

Nemojte koristiti granice rute za:

  • Pad jednog widgeta na stranici
  • Validaciju na razini polja
  • Greške u pozadinskom osvježavanju

UX zahtjevi za route fallback#

Dobar route fallback odgovara na ova pitanja korisnika:

  • Mogu li pokušati ponovno
  • Jesu li moji podaci sigurni
  • Kamo mogu otići umjesto toga

Praktičan predložak:

  • Naslov: “Ovu stranicu nije moguće učitati”
  • Tijelo: kratko i specifično ako znate uzrok, npr. “Vaša sesija je istekla”
  • Gumbi: Retry, Go back, Go to dashboard
  • Opcionalno: kod greške za podršku, npr. ERR_ROUTE_403

# Greške dohvata podataka: ponovni pokušaji koji prate stvarnost#

Većina grešaka koje korisnici vide u React aplikacijama su mrežni ili serverski problemi. Tretiranje svih kvarova kao fatalnih je najbrži put do nepouzdanog UX-a.

U produkcijskoj telemetriji kroz consumer i B2B aplikacije prolazne greške su česte. Stvarne varijacije CDN-ova i ISP-ova znače da treba očekivati sporadične timeoute. Konzervativna pretpostavka dizajna je da će mjerljiv dio zahtjeva povremeno pasti pod opterećenjem ili tijekom deploya.

Vaša strategija:

  • Retry za čitanja koja su sigurna za ponavljanje
  • Bez retry-a za upise osim ako imate idempotentnost
  • Uvijek ograničite broj retry-a i pokažite napredak
  • Koristite eksponencijalni backoff s jitterom kako biste izbjegli “retry oluje”

TanStack Query: produkcijski spremna politika retry-a#

TanStack Query daje finu kontrolu retry logike. Čest baseline koji dobro skalira:

  • Retry 2 puta za mrežne greške i 5xx
  • Bez retry-a za 4xx osim 408 i 429
  • Eksponencijalni backoff s maksimalnim kašnjenjem
TypeScript
import { QueryClient } from "@tanstack/react-query";
 
export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: (failureCount, error: any) => {
        const status = error?.status ?? error?.response?.status;
 
        if (status === 401 || status === 403 || status === 404) return false;
        if (status === 429) return failureCount < 3;
        if (status >= 500) return failureCount < 2;
 
        return failureCount < 2;
      },
      retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 8000),
      staleTime: 30_000,
      gcTime: 10 * 60_000,
      refetchOnWindowFocus: false,
    },
    mutations: {
      retry: false,
    },
  },
});

Ovo sprječava da korisnike “kažnjavate” dugim petljama tipa “loading pa error”. Također smanjuje pritisak na backend jer su retry-evi ograničeni i odgođeni.

Za strategije cacheiranja, paginacije i invalidacije koje smanjuju lance refetchanja sklone greškama, pogledajte React Query invalidacija cachea, paginacija i mutacije na razini skale.

SWR: retry s namjerom, ne po defaultu#

SWR default postavke mogu biti preagresivne za neke proizvode, posebno uz revalidaciju na fokus i nestabilne mreže. Postavite eksplicitne politike.

TypeScript
import useSWR from "swr";
 
const fetcher = (url: string) => fetch(url).then((r) => {
  if (!r.ok) {
    const err: any = new Error("Request failed");
    err.status = r.status;
    throw err;
  }
  return r.json();
});
 
export function useCustomers() {
  return useSWR("/api/customers", fetcher, {
    revalidateOnFocus: false,
    shouldRetryOnError: (err) => {
      if (err?.status === 401 || err?.status === 403 || err?.status === 404) return false;
      return true;
    },
    errorRetryCount: 2,
    errorRetryInterval: 1000,
    dedupingInterval: 2000,
  });
}

Anti-pattern: retry mutacija bez idempotentnosti#

Ako retryate mutaciju poput “Create invoice” nakon timeouta, možete stvoriti duplikate. To nije teorija. Događa se na slabim mobilnim mrežama i tijekom backend deploya.

Ako morate retryati upise:

  • Koristite idempotency keys na serveru
  • Vraćajte stabilne identifikatore
  • Pokažite “processing” stanje umjesto da ponovno šaljete zahtjev

⚠️ Upozorenje: Isključiti retry za mutacije sigurnije je nego ga uključiti naslijepo. Ako ne možete jamčiti idempotentnost, retry je bug integriteta podataka, a ne UX značajka.

# Pretvaranje grešaka podataka u UX: inline, toastovi i tokovi oporavka#

Obrazac koji se dobro skalira razlikuje lokalne i globalne greške.

  • Inline poruke su najbolje kad se greška odnosi na određeni dio UI-ja ili polje forme.
  • Toastovi su najbolji za događaje koji presijecaju aplikaciju, događaju se u pozadini ili su rezultat korisničke akcije koja je već završena.

Inline greške: najbolje za forme, panele i widgete#

Inline poruke trebaju uključiti:

  • Što nije uspjelo, jednostavnim jezikom
  • Sljedeći korak, poput retry ili refresh
  • Opcionalni detalj ako pomaže, npr. “Provjerite vezu”

Obrazac za grešku widgeta:

  • Naslov: “Nije moguće učitati kupce”
  • Gumb: Retry
  • Sekundarno: “Prikaži cacheirane podatke” ako je dostupno

Toastovi: najbolje za ishode spremanja i pozadinske poslove#

Toastovi funkcioniraju kada su:

  • Kratki
  • Usmjereni na akciju
  • Ne ponavljaju se u petljama

Primjeri koji skaliraju:

  • “Saved” s Undo akcijom
  • “Upload failed” s Retry
  • “Session expired” sa Sign in

Anti-pattern: prikazivati toast na svaku grešku refetcha u pozadini. Korisnici dožive “toast spam” i počnu ga ignorirati.

Obrazac: taksonomija poruka i kodovi grešaka#

Napravite zajedničku taksonomiju grešaka kako bi poruke bile dosljedne kroz timove.

Klasa greškeTipičan izvorPoruka korisnikuUI pozicija
AuthError401, 403“Molimo prijavite se ponovno.”Granica rute ili modal
NotFoundError404“Ova stavka više ne postoji.”Inline na stranici detalja
RateLimitError429“Previše zahtjeva. Pokušajte ponovno za minutu.”Inline, ponekad toast
ValidationError422Poruke po poljimaInline po polju
NetworkErroroffline, timeout“Provjerite vezu i pokušajte ponovno.”Inline, ponekad toast
UnknownErrorneočekivano“Nešto je pošlo po zlu.” + opcija prijaveFallback granice

Ova taksonomija olakšava odlučiti treba li retry i gdje prikazati poruku.

# Praktična implementacija: jedan error model koji se koristi svugdje#

Najbrži put do dosljednog ponašanja je normalizirati greške na razini API klijenta.

Normalizirajte Fetch greške u tipizirani oblik#

Neka oblik bude jednostavan i serializable.

TypeScript
export type AppError = {
  name: string;
  message: string;
  status?: number;
  code?: string;
  retriable?: boolean;
};
 
export async function api<T>(url: string, init?: RequestInit): Promise<T> {
  const res = await fetch(url, init);
 
  if (!res.ok) {
    const err: AppError = {
      name: "ApiError",
      message: "Request failed",
      status: res.status,
      retriable: res.status >= 500 || res.status === 429,
    };
    throw err;
  }
 
  return res.json() as Promise<T>;
}

Sada vaš TanStack Query ili SWR kod može temeljiti retry i UI logiku na status i retriable.

Obrazac: bacite grešku u renderu samo za “must have” podatke#

Ako se komponenta ne može renderirati bez podataka, možete baciti grešku iz rendera kako bi je uhvatila error boundary, ali samo ako je greška već u memoriji.

Konceptualni primjer:

  • Hook za podatke vrati error objekt
  • Ako ekran ne može funkcionirati, bacite taj error u renderu
  • Inače pokažite inline error stanje

Ovo izbjegava miješanje boundary logike u svaku child komponentu.

ℹ️ Napomena: Error boundaries i dalje ne hvataju automatski promise rejections. Hook mora već biti u error stanju tijekom renderiranja da bi “throw” obrazac radio.

# Anti-patterns koji se stalno pojavljuju u TanStack Query i SWR#

Ovi obrasci stvaraju nepouzdan UX i otežavaju debugiranje incidenata.

Anti-pattern 1: korištenje onError za toast na svaku query grešku#

Ako se lista refetcha svakih 30 sekundi, tijekom djelomičnog outagea spamate korisnike. Radije koristite inline stanja i toastajte samo na akcije koje je korisnik pokrenuo.

Bolji obrazac:

  • Queryji renderiraju inline error UI unutar pogođenog dijela
  • Mutacije prikazuju toastove jer je korisnik inicirao akciju

Anti-pattern 2: tretirati 404 kao “Error” svugdje#

U mnogim proizvodima 404 je valjan ishod, poput “Nema rezultata” ili “Stavka je obrisana”. Kada ima smisla, mapirajte ga na empty state.

Primjer:

  • Customer not found na stranici detalja treba pokazati ekran “Kupac nije pronađen” s navigacijom
  • Search results 404 treba biti “Nema rezultata”, a ne greška

Anti-pattern 3: beskonačni retry uz focus revalidation#

Default revalidacija na fokus plus retry može stvoriti petlju:

  • Korisnik promijeni tab
  • Aplikacija refetcha
  • Server vrati 500
  • Retry se okine
  • Korisnik opet promijeni tab
  • Još refetcheva

Riješite to eksplicitnim postavkama i ograničenjima, kao ranije.

Anti-pattern 4: brisanje cachea na svaku grešku#

Neki timovi pozivaju queryClient.clear() tijekom auth grešaka. To je disruptivno i može pokrenuti kaskade refetchanja. Radije koristite ciljanu invalidaciju i redirect flow.

Ako trebate razumjeti obrasce invalidacije koji izbjegavaju kaskadne kvarove, koristite ovaj vodič za React Query na razini skale.

# Testiranje rukovanja greškama: tretirajte kvarove kao scenarij prve klase#

Ne možete vjerovati rukovanju greškama koje ne testirate. Praktičan baseline je testirati:

  • Fallback granice widgeta se prikazuje kada child baci grešku
  • Granica rute se prikazuje na loader ili page failure
  • Retry ponašanje queryja je ograničeno
  • Inline error poruke se pojavljuju i retry radi
  • Neuspjele mutacije pokažu jedan toast, ne petlju

Pouzdan toolchain je Vitest, React Testing Library i MSW za mockanje mreže. Workflow je obrađen u React strategiji testiranja s Vitestom, React Testing Library i MSW.

Primjer: test fallbacka granice komponente.

TSX
import { render, screen } from "@testing-library/react";
import React from "react";
import { ErrorBoundary } from "./ErrorBoundary";
 
function Boom() {
  throw new Error("boom");
}
 
it("shows fallback when child throws", () => {
  render(
    <ErrorBoundary
      name="Test"
      fallback={() => <div>Fallback</div>}
    >
      <Boom />
    </ErrorBoundary>
  );
 
  expect(screen.getByText("Fallback")).toBeInTheDocument();
});

# Operativni aspekti: logiranje, korelacija i podrška#

Dobar UX nije dovoljan. Trebate i brzo debugiranje.

Minimalni operativni zahtjevi:

  • Logirajte greške iz granica s kontekstom, poput naziva značajke i rute
  • Uključite correlation ID iz backend odgovora ako postoji
  • Zabilježite je li korisnik kliknuo retry i je li uspjelo

To pretvara “srušilo se” u incident koji se može riješiti.

Pragmatičan pristup je dodati metapodatke u vaš boundary onError i klijente za dohvat podataka, pa ih proslijediti u vaš observability stack. Držite metapodatke stabilnima kako bi dashboardi bili upotrebljivi.

# Ključne poruke#

  • Postavite component error boundaries oko neovisnih widgeta kako jedan kvar ne bi srušio cijelu stranicu.
  • Koristite route boundaries za kvarove na razini stranice i ponudite retry plus sigurnu navigaciju.
  • Namjerno konfigurirajte data retry: retryajte idempotentna čitanja uz ograničenja i backoff, izbjegavajte retry mutacija osim ako imate idempotentnost.
  • Preferirajte inline error stanja za lokalne probleme i toastove za ishode koje je korisnik pokrenuo te događaje koji presijecaju aplikaciju.
  • Standardizirajte taksonomiju grešaka i normalizirani oblik greške kako bi retry logika i UX bili dosljedni kroz timove.

# Zaključak#

React aplikacije u 2026. pucaju na više mjesta nego samo u renderiranju, pa pouzdanost koja se može skalirati dolazi iz slojevitih obrazaca rukovanja greškama u Reactu: granice za izolaciju UI-ja, fallbackovi na razini rute za oporavak stranice, ponovni pokušaji u podatkovnom sloju za prolazne kvarove i UX poruke koje ostaju mirne i usmjerene na akciju.

Ako želite pomoć u implementaciji ovih obrazaca kroz Next.js ili React codebase, Samioda može napraviti audit vaših trenutnih error flowova, ujednačiti politike retry-a za TanStack Query ili SWR i isporučiti dosljedan sustav fallbackova i poruka koji poboljšava i UX i incident response. Javite se putem Samioda kako biste dobili praktičan plan i produkcijski spremnu implementaciju.

FAQ

Share
A
Adrijan OmićevićOsnivač i senior developer

Osnivač i senior developer u Samiodi. 8+ godina iskustva u izradi React, Next.js, Flutter i n8n rješenja za klijente diljem Europe.

Trebate pomoć s projektom?

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