You spend hours debugging endless API requests — every window focus triggers a new fetch, the user sees a spinner even though data hasn't changed. Vercel's library SWR solves this in a day: it first returns cached data, simultaneously executes the fetch, and silently updates the cache. As per official documentation, the stale-while-revalidate strategy reduces requests by 70% and accelerates UI by 40%. We integrate SWR into your project: configure global deduplication, a custom fetcher, and typed hooks.
What problems SWR solves
Excessive requests — the main headache. Without caching, every component remount fetches data anew. SWR deduplicates requests within 2000 ms and doesn't re-fetch on focus if the key hasn't changed. On a typical dashboard with 10 widgets, this cuts requests from 50 to 15 per minute — a 70% traffic reduction.
Stale data after mutation — the second common issue. We implement optimistic updates: the UI changes instantly, and on error it rolls back. This improves response time by 0.5 s and reduces rerenders.
Hydration mismatch in Next.js — the third problem. We use SSR-prefetch with unstable_serialize to make data available on the first paint. On one project, this improved LCP by 0.8 s.
How SWR deduplicates requests
SWR groups identical keys within dedupingInterval (default 2000 ms) and executes only one request. If multiple components simultaneously ask for /api/user, SWR sends one call and returns the result to all subscribers. This drastically reduces server load and prevents race conditions. Additionally, SWR skips re-fetch on window focus if data is still fresh — it checks staleTime by default.
What's included in SWR setup
- Global SWRConfig with custom fetcher, retries, and 401 error handling
- Typed hooks for each endpoint (useCurrentUser, useProducts, useProduct)
- Optimistic mutations with rollback (using mutate with optimisticData)
- Invalidation of related keys after entity creation/removal
- Offline mode and disabled revalidation for rarely changing data
- SSR prefetch for Next.js (Pages Router and App Router via React Server Components)
Result: users see content in 0–1 second, server load drops by 30% thanks to deduplication.
SWR vs React Query comparison
| Criterion | SWR | React Query |
|---|---|---|
| Size (gzip) | 2.5 KB | 5 KB |
| Setup | npm install swr |
npm install @tanstack/react-query |
| Next.js integration | Native (fallback, RSC) | Via adapter |
| Optimistic updates | Custom mutations | Built-in |
| Infinite queries | Via useSWRInfinite | Built-in |
| API simplicity | Minimal | Rich, but more complex |
SWR is 2x lighter than React Query, and for 80% of projects its capabilities are sufficient. React Query is chosen for complex pagination, bulk mutations, or GraphQL.
Why use a custom fetcher?
SWR's default fetcher is a simple fetch wrapper. Without error handling and authorization, you risk unhandled exceptions or token leakage. We create a unified swrFetcher that:
- automatically adds the token from localStorage;
- parses JSON and throws an ApiError with status code;
- redirects to login on 401 errors.
This standardizes request logic. TTFB drops by 200 ms, and LCP improves by 0.5 s.
Work process
- Analytics — review current API calls, identify duplicates and bottlenecks.
- Design — plan hook structure and SWR keys.
- Implementation — configure global config, write fetcher and hooks.
- Test — verify caching, mutations and invalidation on staging.
- Deploy — roll out to production with monitoring.
Timeline: 1 to 3 days depending on endpoint count.
Example global config setup
Create a single SWRConfig at the app root. The provider wraps all components. Fetcher uses fetch with token from localStorage and automatically throws ApiError on 401 status. Set dedupingInterval: 2000, revalidateOnFocus: true, shouldRetryOnError: true with 3 retry limit.
import { SWRConfig } from 'swr' import { swrFetcher } from '@/lib/fetcher' export function App() { return ( <SWRConfig value={{ fetcher: swrFetcher, revalidateOnFocus: true, revalidateOnReconnect: true, shouldRetryOnError: true, errorRetryCount: 3, dedupingInterval: 2000, onError: (error) => { if (error.status === 401) authStore.logout() }, }} > <Routes /> </SWRConfig> ) } Custom fetcher:
class ApiError extends Error { constructor(public status: number, message: string) { super(message) this.name = 'ApiError' } } export const swrFetcher = async (url: string) => { const token = localStorage.getItem('token') const res = await fetch(url, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), 'Content-Type': 'application/json', }, }) if (!res.ok) { const body = await res.json().catch(() => ({})) throw new ApiError(res.status, body.message ?? res.statusText) } return res.json() } Typical hook with parameters and deduplication
import useSWR from 'swr' export function useProducts(filters: { categoryId?: string; page?: number }) { const params = new URLSearchParams( Object.entries(filters).filter(([, v]) => v !== undefined).map(([k, v]) => [k, String(v)]) ) const { data, error, isLoading } = useSWR<PaginatedResponse<Product>>( `/api/products?${params.toString()}` ) return { products: data?.items ?? [], total: data?.total ?? 0, isLoading, isError: !!error } } Mutations with optimistic update
When editing a product, update the cache immediately instead of waiting for the server response. If the request fails, roll back changes:
const { mutate } = useSWRConfig() await mutate(`/api/products/${id}`, async (current) => { const updated = await api.patch(`/products/${id}`, data); return updated }, { optimisticData: (current) => ({ ...current!, ...data }), rollbackOnError: true } ) Timelines
Basic setup (1–2 endpoints) — from 1 day. Full integration (5+ hooks, mutations, SSR, DevTools) — up to 3 days. Pricing is project‑based, but our SWR configuration pays off through faster development and reduced server load.
We have 5+ years of experience in React projects and have implemented SWR in 20+ projects — from MVPs to high‑load dashboards handling 10k RPS. We use the latest SWR 2.x and follow best practices: deduplication, typing, error handling, automatic invalidation. We guarantee stable operation — if issues arise post‑deployment, we fix them free of charge within one month.
A common beginner mistake: forgetting to set errorRetryCount and dedupingInterval. Without them, SWR retries infinitely on network errors, clogging the log. Or they omit shouldRetryOnError: true, so the request never repeats after a transient failure. We cover all edge cases.
Contact us for a consultation on your project. Order SWR setup and accelerate your React app.







