SWR
Reference for SWR v2, the React Hooks library for stale-while-revalidate data
fetching. Prefer the project's existing data layer, fetchers, and hook
conventions; apply these rules on top so cache identity, revalidation, and
mutation behavior stay predictable.
Branch-specific references, loaded on demand:
- pagination.md —
useSWRInfinite for paginated and
cursor-based lists.
- subscriptions.md —
useSWRSubscription for WebSocket
and realtime sources.
- nextjs.md — App Router boundaries, server prefetch, and
fallback hydration.
First Checks
- Confirm
swr is installed (package.json, lockfile) and which package
manager the project uses.
- Find existing fetchers, API clients, auth token handling, error types, and
reusable data hooks before introducing new patterns.
- In Next.js App Router, identify the client/server boundary first — see
nextjs.md.
Keys: The Key Is The Cache Identity
The SWR key is the sole identity of a cached resource. Every input that
changes the response belongs in the key: URL, query params, user scope, locale,
tenant, auth token, filters. A value read inside the fetcher but absent from
the key returns data under the wrong identity.
| Case |
Pattern |
Why |
| Simple resource |
useSWR('/api/user', fetcher) |
String key is passed to fetcher. |
| Conditional fetch |
useSWR(userId ? ['/api/user', userId] : null, fetcher) |
null disables the request while the hook call stays unconditional. |
| Dependent fetch |
useSWR(() => user.id ? ['/api/projects', user.id] : null, fetcher) |
Function keys wait for required data. |
| Auth or scope |
useSWR(['/api/user', token], ([url, token]) => fetchWithToken(url, token)) |
Scope-changing inputs are part of the identity. |
| Object filters |
useSWR({ url: '/api/search', filters }, fetcher) |
Object-like keys are serialized by SWR. |
Rules:
- Call SWR hooks unconditionally; put the condition in the key (
null or a
function key that returns null).
- For array keys in SWR v2, the fetcher receives the full array, not spread
arguments.
- Keep keys stable, serializable, and specific: when filters change the
result, the key changes with them.
- Use a shared fetcher through
SWRConfig when most hooks use the same
transport; use local fetchers for special auth, GraphQL, or non-JSON
responses.
Core Pattern
Prefer small reusable hooks that hide SWR details from presentation
components:
import useSWR from 'swr'
type User = {
id: string
name: string
}
const fetcher = async <T,>(url: string): Promise<T> => {
const response = await fetch(url)
if (!response.ok) {
const error = new Error('Failed to fetch data')
throw Object.assign(error, {
status: response.status,
info: await response.json().catch(() => undefined),
})
}
return response.json()
}
export function useUser(userId: string | null) {
const { data, error, isLoading, isValidating, mutate } = useSWR<User>(
userId ? `/api/users/${userId}` : null,
fetcher,
)
return {
user: data,
error,
isLoading,
isValidating,
refreshUser: mutate,
}
}
- Fetchers throw on failed responses and preserve status/error details.
- Use
isLoading for first-load UI, isValidating for background-refresh
indicators over already-rendered stale data.
data and error can coexist after a failed revalidation: keep useful
stale data visible rather than replacing it with an error screen.
- Return domain names (
user, todos) from reusable hooks instead of leaking
raw SWR property names; annotate useSWR<Data, ErrorType> when the fetcher
cannot infer the response type.
Global Configuration
Use SWRConfig for app-wide defaults:
import { SWRConfig } from 'swr'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
fetcher: (url: string) => fetch(url).then(response => {
if (!response.ok) throw new Error('Request failed')
return response.json()
}),
revalidateOnFocus: true,
revalidateOnReconnect: true,
shouldRetryOnError: true,
}}
>
{children}
</SWRConfig>
)
}
fallback takes a map of prefetched values keyed by SWR cache keys;
fallbackData is one hook's local initial value.
- Keep
revalidateOnFocus on globally unless stale data is acceptable across
the product.
- Tune
dedupingInterval, refreshInterval, retry behavior, and focus
throttling per endpoint cost and freshness needs.
- With custom cache providers, global
mutate broadcasts within the provider
scope.
Mutations
Choose mutation APIs by intent:
| Need |
API |
Notes |
| Revalidate an existing resource |
mutate(key) or bound mutate() |
Marks the resource stale and refetches. |
| Update local cache after a known change |
bound mutate(nextData) |
Works well after an already-completed request. |
| Optimistic UI |
mutate(asyncUpdate, { optimisticData, rollbackOnError }) |
Use rollback for failed remote writes. |
| User-triggered remote write |
useSWRMutation(key, mutationFetcher) |
Runs only on trigger(arg). |
Optimistic update pattern:
await mutate(
async current => {
const updated = await updateTodo(todoId, { completed: true })
return current?.map(todo => (todo.id === todoId ? updated : todo))
},
{
optimisticData: current =>
current?.map(todo =>
todo.id === todoId ? { ...todo, completed: true } : todo,
),
rollbackOnError: true,
populateCache: true,
revalidate: false,
},
)
Mutation fetchers receive their payload as arg — type it:
import useSWRMutation from 'swr/mutation'
type UpdateUserInput = { name: string }
async function updateUser(
url: string,
{ arg }: { arg: UpdateUserInput },
) {
const response = await fetch(url, {
method: 'PATCH',
body: JSON.stringify(arg),
})
if (!response.ok) throw new Error('Failed to update user')
return response.json()
}
const { trigger, isMutating } = useSWRMutation('/api/user', updateUser)
Guidelines:
- Prefer bound
mutate from the related useSWR hook when changing the same
resource; use useSWRConfig().mutate for cross-component invalidation, such
as after logout or a global settings change.
- After create/delete operations, invalidate every affected list and detail
key — a filter function key can target multiple cached resources at once.
- Keep optimistic data shape identical to the resolved data shape so no UI
branch exists only during mutation.
- Surface
isMutating to disable duplicate submits.
Review Checklist
- Every SWR hook has a key that includes all data-shaping inputs.
- Conditional behavior lives in the key (
null or function keys); every hook
call is unconditional.
- Fetchers throw on failed responses and preserve useful status/error details.
- First-load, background-refresh, empty, stale-with-error, and mutation states
have coherent UI behavior.
- Mutations update or invalidate all affected cache keys and handle rollback.
- App Router hook usage is isolated to Client Components;
fallback keys
match hook keys, including serialized complex keys (nextjs.md).
- Pagination handles end-of-list, filter changes, loading-more state, and list
item updates (pagination.md).
- Revalidation and retry settings match endpoint freshness and cost.
1---2name: swr3description: SWR v2 (stale-while-revalidate) data fetching for React and Next.js. Use when implementing or reviewing code that uses the `swr` package — useSWR hooks, cache keys, revalidation, mutations, pagination, or SWRConfig.4---56# SWR78Reference for SWR v2, the React Hooks library for stale-while-revalidate data9fetching. Prefer the project's existing data layer, fetchers, and hook10conventions; apply these rules on top so cache identity, revalidation, and11mutation behavior stay predictable.1213Branch-specific references, loaded on demand:1415- [pagination.md](./pagination.md) — `useSWRInfinite` for paginated and16 cursor-based lists.17- [subscriptions.md](./subscriptions.md) — `useSWRSubscription` for WebSocket18 and realtime sources.19- [nextjs.md](./nextjs.md) — App Router boundaries, server prefetch, and20 `fallback` hydration.2122## First Checks23241. Confirm `swr` is installed (`package.json`, lockfile) and which package25 manager the project uses.262. Find existing fetchers, API clients, auth token handling, error types, and27 reusable data hooks before introducing new patterns.283. In Next.js App Router, identify the client/server boundary first — see29 [nextjs.md](./nextjs.md).3031## Keys: The Key Is The Cache Identity3233The SWR `key` is the sole identity of a cached resource. Every input that34changes the response belongs in the key: URL, query params, user scope, locale,35tenant, auth token, filters. A value read inside the fetcher but absent from36the key returns data under the wrong identity.3738| Case | Pattern | Why |39|------|---------|-----|40| Simple resource | `useSWR('/api/user', fetcher)` | String key is passed to `fetcher`. |41| Conditional fetch | `useSWR(userId ? ['/api/user', userId] : null, fetcher)` | `null` disables the request while the hook call stays unconditional. |42| Dependent fetch | `useSWR(() => user.id ? ['/api/projects', user.id] : null, fetcher)` | Function keys wait for required data. |43| Auth or scope | `useSWR(['/api/user', token], ([url, token]) => fetchWithToken(url, token))` | Scope-changing inputs are part of the identity. |44| Object filters | `useSWR({ url: '/api/search', filters }, fetcher)` | Object-like keys are serialized by SWR. |4546Rules:4748- Call SWR hooks unconditionally; put the condition in the key (`null` or a49 function key that returns `null`).50- For array keys in SWR v2, the fetcher receives the full array, not spread51 arguments.52- Keep keys stable, serializable, and specific: when filters change the53 result, the key changes with them.54- Use a shared fetcher through `SWRConfig` when most hooks use the same55 transport; use local fetchers for special auth, GraphQL, or non-JSON56 responses.5758## Core Pattern5960Prefer small reusable hooks that hide SWR details from presentation61components:6263```tsx64import useSWR from 'swr'6566type User = {67 id: string68 name: string69}7071const fetcher = async <T,>(url: string): Promise<T> => {72 const response = await fetch(url)7374 if (!response.ok) {75 const error = new Error('Failed to fetch data')76 throw Object.assign(error, {77 status: response.status,78 info: await response.json().catch(() => undefined),79 })80 }8182 return response.json()83}8485export function useUser(userId: string | null) {86 const { data, error, isLoading, isValidating, mutate } = useSWR<User>(87 userId ? `/api/users/${userId}` : null,88 fetcher,89 )9091 return {92 user: data,93 error,94 isLoading,95 isValidating,96 refreshUser: mutate,97 }98}99```100101- Fetchers throw on failed responses and preserve status/error details.102- Use `isLoading` for first-load UI, `isValidating` for background-refresh103 indicators over already-rendered stale data.104- `data` and `error` can coexist after a failed revalidation: keep useful105 stale data visible rather than replacing it with an error screen.106- Return domain names (`user`, `todos`) from reusable hooks instead of leaking107 raw SWR property names; annotate `useSWR<Data, ErrorType>` when the fetcher108 cannot infer the response type.109110## Global Configuration111112Use `SWRConfig` for app-wide defaults:113114```tsx115import { SWRConfig } from 'swr'116117export function Providers({ children }: { children: React.ReactNode }) {118 return (119 <SWRConfig120 value={{121 fetcher: (url: string) => fetch(url).then(response => {122 if (!response.ok) throw new Error('Request failed')123 return response.json()124 }),125 revalidateOnFocus: true,126 revalidateOnReconnect: true,127 shouldRetryOnError: true,128 }}129 >130 {children}131 </SWRConfig>132 )133}134```135136- `fallback` takes a map of prefetched values keyed by SWR cache keys;137 `fallbackData` is one hook's local initial value.138- Keep `revalidateOnFocus` on globally unless stale data is acceptable across139 the product.140- Tune `dedupingInterval`, `refreshInterval`, retry behavior, and focus141 throttling per endpoint cost and freshness needs.142- With custom cache providers, global `mutate` broadcasts within the provider143 scope.144145## Mutations146147Choose mutation APIs by intent:148149| Need | API | Notes |150|------|-----|-------|151| Revalidate an existing resource | `mutate(key)` or bound `mutate()` | Marks the resource stale and refetches. |152| Update local cache after a known change | bound `mutate(nextData)` | Works well after an already-completed request. |153| Optimistic UI | `mutate(asyncUpdate, { optimisticData, rollbackOnError })` | Use rollback for failed remote writes. |154| User-triggered remote write | `useSWRMutation(key, mutationFetcher)` | Runs only on `trigger(arg)`. |155156Optimistic update pattern:157158```tsx159await mutate(160 async current => {161 const updated = await updateTodo(todoId, { completed: true })162 return current?.map(todo => (todo.id === todoId ? updated : todo))163 },164 {165 optimisticData: current =>166 current?.map(todo =>167 todo.id === todoId ? { ...todo, completed: true } : todo,168 ),169 rollbackOnError: true,170 populateCache: true,171 revalidate: false,172 },173)174```175176Mutation fetchers receive their payload as `arg` — type it:177178```tsx179import useSWRMutation from 'swr/mutation'180181type UpdateUserInput = { name: string }182183async function updateUser(184 url: string,185 { arg }: { arg: UpdateUserInput },186) {187 const response = await fetch(url, {188 method: 'PATCH',189 body: JSON.stringify(arg),190 })191192 if (!response.ok) throw new Error('Failed to update user')193 return response.json()194}195196const { trigger, isMutating } = useSWRMutation('/api/user', updateUser)197```198199Guidelines:200201- Prefer bound `mutate` from the related `useSWR` hook when changing the same202 resource; use `useSWRConfig().mutate` for cross-component invalidation, such203 as after logout or a global settings change.204- After create/delete operations, invalidate every affected list and detail205 key — a filter function key can target multiple cached resources at once.206- Keep optimistic data shape identical to the resolved data shape so no UI207 branch exists only during mutation.208- Surface `isMutating` to disable duplicate submits.209210## Review Checklist211212- Every SWR hook has a key that includes all data-shaping inputs.213- Conditional behavior lives in the key (`null` or function keys); every hook214 call is unconditional.215- Fetchers throw on failed responses and preserve useful status/error details.216- First-load, background-refresh, empty, stale-with-error, and mutation states217 have coherent UI behavior.218- Mutations update or invalidate all affected cache keys and handle rollback.219- App Router hook usage is isolated to Client Components; `fallback` keys220 match hook keys, including serialized complex keys ([nextjs.md](./nextjs.md)).221- Pagination handles end-of-list, filter changes, loading-more state, and list222 item updates ([pagination.md](./pagination.md)).223- Revalidation and retry settings match endpoint freshness and cost.