SWR Setup and Configuration
Recipe
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>
);
}Working Example
"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>;
}Deep Dive
How It Works
SWRConfiguses React Context under the hood to propagate configuration values down the component tree.- Any
useSWRcall without an explicitfetcherwill fall back to the nearestSWRConfigprovider's fetcher. - Multiple
SWRConfigproviders can be nested; inner values merge with and override outer values. - The
dedupingIntervalprevents duplicate requests to the same key within the specified milliseconds. revalidateOnFocustriggers a background revalidation when the browser tab regains focus.
Variations
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>TypeScript Notes
SWRConfigacceptsvaluetyped asPartial<SWRConfiguration>or a function returning one.- Import
SWRConfigurationfromswrto type config objects explicitly.
import type { SWRConfiguration } from "swr";
const config: SWRConfiguration = {
fetcher: (url: string) => fetch(url).then((r) => r.json()),
revalidateOnFocus: false,
};Gotchas
- Forgetting to add
"use client"on the provider file in Next.js App Router will cause a runtime error becauseSWRConfiguses React Context. - If your fetcher throws on non-2xx responses, SWR treats that as an error. If it does not throw,
errorwill always beundefinedeven on 404s. dedupingIntervalis not the same as caching. It only deduplicates in-flight requests within the interval window.- Setting
refreshIntervalat the global level applies to every hook. Prefer setting it per-hook unless you genuinely need global polling.
Alternatives
| 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 |
FAQs
Why does the SWRConfig provider file need "use client" in Next.js App Router?
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.
What does the dedupingInterval option actually do?
- It deduplicates in-flight requests to the same key within the specified millisecond window (default 2000ms).
- If two components mount and call
useSWRwith the same key within that window, only one network request is made. - It is not the same as caching -- it only prevents concurrent duplicate requests.
How do nested SWRConfig providers merge their values?
Inner SWRConfig values merge with and override outer values. For example, an inner provider can set refreshInterval while inheriting the outer provider's fetcher.
What happens if the fetcher does not throw on non-2xx HTTP responses?
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.
How can I access the parent SWRConfig values when defining a nested config?
Pass a function instead of an object to the value prop:
<SWRConfig
value={(parentConfig) => ({
...parentConfig,
refreshInterval: 5000,
})}
>
{children}
</SWRConfig>Is it safe to set refreshInterval globally in 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.
How do I type the SWRConfig value object in TypeScript?
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,
};What TypeScript type does the SWRConfig value prop accept?
It accepts Partial<SWRConfiguration> or a function (parentConfig: SWRConfiguration) => SWRConfiguration that receives the parent config and returns a new one.
Can I add authorization headers to the global fetcher?
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();
});Gotcha: What is a common mistake when omitting the fetcher from individual useSWR calls?
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.
Where should the SWRProvider be placed in a Next.js App Router project?
Wrap it around {children} in your root app/layout.tsx so every page and component inherits the shared configuration.