Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Install SWR, wrap your app with SWRConfig, and set global defaults so every useSWR call inherits a shared fetcher and options.
npm install swr// app/providers.tsx
"use client";
import { SWRConfig } from "swr";
const fetcher = (url: string) => fetch(url).then((res) => res.json());
export function SWRProvider({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
dedupingInterval: 2000,
}}
>
{children}
</SWRConfig>
);
}// app/layout.tsx
import { SWRProvider } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SWRProvider>{children}</SWRProvider>
</body>
</html>
);
}"use client";
import useSWR from "swr";
function UserProfile({ userId }: { userId: string }) {
// fetcher is inherited from SWRConfig
const { data, error, isLoading } = useSWR(`/api/users/${userId}`);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Failed to load</div>;
return <div>Hello, {data.name}!</div>;
}SWRConfig uses React Context under the hood to propagate configuration values down the component tree.useSWR call without an explicit fetcher will fall back to the nearest SWRConfig provider's fetcher.SWRConfig providers can be nested; inner values merge with and override outer values.dedupingInterval prevents duplicate requests to the same key within the specified milliseconds.revalidateOnFocus triggers a background revalidation when the browser tab regains focus.Custom fetcher with headers:
const authedFetcher = (url: string) =>
fetch(url, {
headers: { Authorization: `Bearer ${getToken()}` },
}).then((res) => {
if (!res.ok) throw new Error("Fetch failed");
return res.json();
});Nested config override:
<SWRConfig value={{ fetcher: globalFetcher }}>
<SWRConfig value={{ refreshInterval: 3000 }}>
{/* Components here get globalFetcher + 3s refresh */}
</SWRConfig>
</SWRConfig>Functional config (access parent):
<SWRConfig
value={(parentConfig) => ({
...parentConfig,
refreshInterval: 5000,
})}
>
{children}
</SWRConfig>SWRConfig accepts value typed as Partial<SWRConfiguration> or a function returning one.SWRConfiguration from swr to type config objects explicitly.import type { SWRConfiguration } from "swr";
const config: SWRConfiguration = {
fetcher: (url: string) => fetch(url).then((r) => r.json()),
revalidateOnFocus: false,
};"use client" on the provider file in Next.js App Router will cause a runtime error because SWRConfig uses React Context.error will always be undefined even on 404s.dedupingInterval is not the same as caching. It only deduplicates in-flight requests within the interval window.refreshInterval at the global level applies to every hook. Prefer setting it per-hook unless you genuinely need global polling.
| Approach | Pros | Cons |
|---|---|---|
| Global SWRConfig | Single setup, consistent defaults | All hooks share config; can be too broad |
| Per-hook config | Fine-grained control per query | Repetitive fetcher declarations |
| Custom hook wrapper | Centralized logic with flexibility | Extra abstraction layer |
SWRConfig uses React Context internally, which is a client-side API. Without "use client", Next.js treats the file as a Server Component and throws a runtime error when Context is used.
useSWR with the same key within that window, only one network request is made.Inner SWRConfig values merge with and override outer values. For example, an inner provider can set refreshInterval while inheriting the outer provider's fetcher.
SWR will treat the response as successful data. The error value will always be undefined, even on 404 or 500 responses. Always check res.ok in your fetcher and throw if it is false.
Pass a function instead of an object to the value prop:
<SWRConfig
value={(parentConfig) => ({
...parentConfig,
refreshInterval: 5000,
})}
>
{children}
</SWRConfig>It works, but it applies polling to every useSWR hook in the tree. Prefer setting refreshInterval per-hook unless you genuinely need all queries to poll at the same rate.
Import SWRConfiguration from swr and use it as the type:
import type { SWRConfiguration } from "swr";
const config: SWRConfiguration = {
fetcher: (url: string) => fetch(url).then((r) => r.json()),
revalidateOnFocus: false,
};It accepts Partial<SWRConfiguration> or a function (parentConfig: SWRConfiguration) => SWRConfiguration that receives the parent config and returns a new one.
Yes. Define a custom fetcher that includes headers:
const authedFetcher = (url: string) =>
fetch(url, {
headers: { Authorization: `Bearer ${getToken()}` },
}).then((res) => {
if (!res.ok) throw new Error("Fetch failed");
return res.json();
});If there is no SWRConfig provider wrapping the component (or the provider does not define a fetcher), useSWR will have no fetcher and silently never fetch data. Always ensure a provider with a fetcher exists above your components.
Wrap it around {children} in your root app/layout.tsx so every page and component inherits the shared configuration.
Reviewed by Chris St. John·Last updated Jul 19, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥