Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Use the useSWR hook with a key and a fetcher function to declaratively fetch, cache, and revalidate remote data in React components.
"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((res) => res.json());
function Dashboard() {
const { data, error, isLoading, isValidating } = useSWR("/api/dashboard", fetcher);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>{data.title}</h1>
{isValidating && <span>Refreshing...</span>}
</div>
);
}"use client";
import useSWR from "swr";
interface User {
id: number;
name: string;
email: string;
}
const fetcher = (url: string): Promise<User[]> =>
fetch(url).then((res) => {
if (!res.ok) throw new Error("Network response was not ok");
return res.json();
});
export default function UserList() {
const { data: users, error, isLoading } = useSWR<User[]>("/api/users", fetcher);
if (isLoading) return <div className="skeleton" />;
if (error) return <div role="alert">Failed to load users</div>;
return (
<ul>
{users?.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
);
}useSWR is the key - a unique string (or array, or null) that identifies the request and drives the cache.isLoading is true only on the first load when there is no cached data yet.isValidating is true whenever a request is in flight, including background revalidations.dedupingInterval window (default: 2000ms).Array keys (multi-argument fetcher):
const fetcher = ([url, token]: [string, string]) =>
fetch(url, { headers: { Authorization: `Bearer ${token}` } }).then((r) => r.json());
const { data } = useSWR(["/api/user", token], fetcher);Using axios:
import axios from "axios";
const fetcher = (url: string) => axios.get(url).then((res) => res.data);
const { data } = useSWR("/api/posts", fetcher);GraphQL fetcher:
const fetcher = (query: string) =>
fetch("/api/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
})
.then((res) => res.json())
.then((json) => json.data);
const { data } = useSWR("{ user { name } }", fetcher);useSWR to type the return: useSWR<Data, Error>(key, fetcher).Data generic.string, [string, ...args], or a function returning either.import useSWR, { Fetcher } from "swr";
const fetcher: Fetcher<User[], string> = (url) =>
fetch(url).then((r) => r.json());
const { data } = useSWR<User[]>("/api/users", fetcher);
// data is User[] | undefineduseSWR([url, id], ...)) is fine because SWR serializes array keys internally, but object keys are not supported.res.ok in your fetcher.data is undefined during the initial load. Always handle the loading state or use optional chaining.useSWR in a Server Component will error. SWR hooks are client-only.| Approach | Pros | Cons |
|---|---|---|
| useSWR | Automatic caching, dedup, revalidation | Client-only, extra bundle size |
| fetch in useEffect | No dependencies | No caching, race conditions, boilerplate |
| React Query (TanStack) | Similar features, more built-in mutations | Larger API surface |
| Server Components fetch | Zero client JS, cached at server | No real-time updates |
From a production Next.js 15 / React 19 SaaS application (SystemsArchitect.io).
// Production example: Search with SWR + debouncing
// File: src/hooks/use-search.ts
export function useSearch() {
const [debouncedQuery, setDebouncedQuery] = useState(query);
useEffect(() => {
const timer = setTimeout(() => setDebouncedQuery(query), 300);
return () => clearTimeout(timer);
}, [query]);
const shouldFetch = debouncedQuery.length >= 3;
const apiUrl = shouldFetch
? `/api/search?q=${encodeURIComponent(debouncedQuery)}&platform=${platform}`
: null;
const { data, error, isLoading, isValidating } = useSWR<SearchResponse>(
apiUrl,
fetcher,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 5000,
keepPreviousData: true,
}
);
return useMemo(() => ({
results: data?.results,
isLoading: isLoading || (shouldFetch && isValidating),
}), [data, isLoading, shouldFetch, isValidating]);
}What this demonstrates in production:
null as the SWR key prevents fetching entirely. This is the idiomatic way to conditionally fetch: when shouldFetch is false, the URL is null and SWR skips the request.keepPreviousData: true shows the previous search results while a new query is in flight, avoiding a blank screen flash between searches.dedupingInterval: 5000 prevents duplicate requests for the same key within 5 seconds. If the user types "react", deletes it, and types "react" again within 5 seconds, SWR returns the cached result instead of hitting the API.revalidateOnFocus: false and revalidateOnReconnect: false disable automatic background revalidation. For search, stale results are acceptable and re-fetching on tab focus would be surprising to users.useMemo on the return object prevents consumer components from re-rendering when the hook's internal state changes but the returned values have not changed.isLoading (first load) with isValidating (background fetch) for a complete loading indicator.isLoading is true only on the first load when there is no cached data.isValidating is true whenever a request is in flight, including background revalidations.isLoading is false but isValidating is true.No. Object keys are not supported because SWR cannot serialize them reliably. Use strings or arrays instead. Array keys are serialized internally, so ["/api/users", id] works fine.
When multiple components call useSWR with the same key within the dedupingInterval window (default 2000ms), only one network request is made. All hooks receive the same promise result.
SWR will treat 404 and 500 responses as successful data. The error field will remain undefined. Always validate res.ok and throw an error for non-2xx responses.
No. useSWR is a React hook that requires client-side rendering. Calling it in a Server Component will throw an error. Use fetch directly in Server Components and pass data as props.
const fetcher = ([url, token]: [string, string]) =>
fetch(url, {
headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.json());
const { data } = useSWR(["/api/user", token], fetcher);It preserves the previous data while a new request is in flight (e.g., when the key changes). This avoids a blank screen flash between key transitions, which is especially useful for search UIs.
import useSWR, { Fetcher } from "swr";
const fetcher: Fetcher<User[], string> = (url) =>
fetch(url).then((r) => r.json());
const { data } = useSWR<User[]>("/api/users", fetcher);
// data is User[] | undefinedFetcher<Data, Key> enforces that the fetcher accepts the key type as input and returns Promise<Data>. It provides compile-time type safety between the key, fetcher, and returned data.
data is always undefined during the initial load before the fetcher resolves. Always handle the loading state with isLoading or use optional chaining (data?.field) to avoid runtime errors.
Reviewed by Chris St. John·Last updated Jul 10, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥