Skip to main content

Next.js SWR

Introductionā€‹

SWR (stale-while-revalidate) is a React hooks library for data fetching created by the team behind Next.js. The name "SWR" comes from the HTTP cache invalidation strategy "stale-while-revalidate", which serves stale (cached) data first, then sends a request to fetch fresh data and updates the page once the new data arrives.

SWR provides a simple and powerful API for fetching data in React applications. It handles caching, revalidation, focus tracking, refetching on interval, and many other features out of the box, making it an excellent choice for data fetching in Next.js applications.

Key Features of SWRā€‹

  • šŸ”„ Automatic Revalidation: Updates data when users switch between tabs or reconnect to the network
  • šŸ“Š Realtime Experience: Continuously refreshes data without affecting user experience
  • āš” Fast Page Navigation: Data is shared and persisted between pages
  • šŸ”„ Reusable Data Fetching: Deduplicate requests for the same data across components
  • šŸ§  Smart Error Retry: Intelligent handling of error states and retry logic
  • šŸ’» TypeScript Ready: Fully typed API for a better developer experience

Getting Started with SWRā€‹

Installationā€‹

First, install SWR in your Next.js project:

bash
npm install swr
# or
yarn add swr
# or
pnpm add swr

Basic Usageā€‹

Here's a simple example of using SWR to fetch data:

jsx
import useSWR from 'swr'

// Define a fetcher function
const fetcher = (...args) => fetch(...args).then(res => res.json())

function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)

if (error) return <div>Failed to load</div>
if (isLoading) return <div>Loading...</div>

// render data
return (
<div>
<h1>Hello {data.name}!</h1>
<p>Email: {data.email}</p>
</div>
)
}

In this example:

  • useSWR accepts a key ('/api/user') and a fetcher function
  • The hook returns 3 values: data, error, and isLoading
  • Based on these values, we can render different UI states

How SWR Worksā€‹

SWR follows the "stale-while-revalidate" strategy:

  1. Return cached data (stale) immediately if available
  2. Send the fetch request to get fresh data
  3. Update the cache and UI with the fresh data when it arrives

This approach ensures that the UI is responsive while keeping data up-to-date.

Advanced Featuresā€‹

Data Revalidationā€‹

SWR provides several ways to revalidate data:

Revalidate on Focusā€‹

When users focus the window or tab, SWR automatically refreshes the data by default.

jsx
import useSWR from 'swr'

function App() {
const { data } = useSWR('/api/data', fetcher, {
revalidateOnFocus: true // This is the default
})
// ...
}

Revalidate on Intervalā€‹

You can set up a polling interval to revalidate data periodically:

jsx
import useSWR from 'swr'

function Dashboard() {
const { data } = useSWR('/api/dashboard', fetcher, {
refreshInterval: 3000 // Refresh every 3 seconds
})
// ...
}

Manual Revalidationā€‹

You can trigger revalidation manually using the mutate function:

jsx
import useSWR, { mutate } from 'swr'

function Profile() {
const { data } = useSWR('/api/user', fetcher)

return (
<div>
<h1>Hi {data?.name}!</h1>
<button onClick={() => mutate('/api/user')}>
Refresh User Data
</button>
</div>
)
}

Mutation and Optimistic Updatesā€‹

SWR makes it easy to update local data and revalidate:

jsx
import useSWR, { mutate } from 'swr'

function Profile() {
const { data, mutate: mutateUser } = useSWR('/api/user', fetcher)

async function updateUsername(newName) {
// Update locally first for immediate UI feedback
mutateUser({ ...data, name: newName }, false)

// Send request to update the server
await fetch('/api/user', {
method: 'PATCH',
body: JSON.stringify({ name: newName })
})

// Trigger revalidation to make sure local data is correct
mutateUser()
}

return (
<div>
<h1>Hello, {data?.name}!</h1>
<button onClick={() => updateUsername('New Name')}>
Change Name
</button>
</div>
)
}

Error Handling and Retriesā€‹

SWR provides built-in retry logic for failed requests:

jsx
import useSWR from 'swr'

function Dashboard() {
const { data, error } = useSWR('/api/dashboard', fetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
// Never retry on 404
if (error.status === 404) return

// Only retry up to 3 times
if (retryCount >= 3) return

// Retry after 5 seconds
setTimeout(() => revalidate({ retryCount }), 5000)
}
})

if (error) return <div>Error loading dashboard</div>
if (!data) return <div>Loading dashboard...</div>

return <Dashboard data={data} />
}

Dependent Fetchingā€‹

Sometimes you need to fetch data that depends on other data:

jsx
import useSWR from 'swr'

function UserPosts() {
// First, fetch the user
const { data: user } = useSWR('/api/user', fetcher)

// Then fetch the user's posts, but only if we have the user's ID
const { data: posts } = useSWR(
user ? `/api/posts?userId=${user.id}` : null,
fetcher
)

if (!user) return <div>Loading user...</div>
if (!posts) return <div>Loading posts...</div>

return (
<>
<h1>User: {user.name}</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</>
)
}

Real-World Example: Building a Dashboardā€‹

Let's create a dashboard with real-time data updates:

jsx
import useSWR from 'swr'

// Define fetcher function
const fetcher = url => fetch(url).then(res => res.json())

function Dashboard() {
// Fetch statistics with automatic refresh every 5 seconds
const { data: stats, error: statsError } = useSWR('/api/stats', fetcher, {
refreshInterval: 5000
})

// Fetch user data only when page loads/focuses
const { data: user, error: userError } = useSWR('/api/user', fetcher)

// Fetch notifications with revalidation on focus
const { data: notifications, error: notifError } = useSWR('/api/notifications', fetcher, {
revalidateOnFocus: true
})

// Loading state
if (!stats || !user || !notifications) {
return <div>Loading dashboard...</div>
}

// Error state
if (statsError || userError || notifError) {
return <div>Error loading dashboard data</div>
}

return (
<div className="dashboard">
<header>
<h1>Welcome, {user.name}!</h1>
<div className="notifications">
{notifications.length} new notifications
</div>
</header>

<div className="stats">
<div className="stat-card">
<h2>Views</h2>
<p className="stat-value">{stats.views.toLocaleString()}</p>
</div>
<div className="stat-card">
<h2>Conversions</h2>
<p className="stat-value">{stats.conversions.toLocaleString()}</p>
</div>
<div className="stat-card">
<h2>Revenue</h2>
<p className="stat-value">${stats.revenue.toLocaleString()}</p>
</div>
</div>

{/* More dashboard content */}
</div>
)
}

export default Dashboard

Global Configuration with SWR Providerā€‹

For application-wide configurations, you can use the SWRConfig provider:

jsx
import { SWRConfig } from 'swr'

function MyApp({ Component, pageProps }) {
return (
<SWRConfig
value={{
fetcher: (url) => fetch(url).then(res => res.json()),
refreshInterval: 3000,
dedupingInterval: 2000,
revalidateOnFocus: true,
revalidateOnReconnect: true,
onError: (error, key) => {
console.error("SWR Error:", error, "for key:", key)
}
}}
>
<Component {...pageProps} />
</SWRConfig>
)
}

export default MyApp

With this configuration, all SWR hooks in your application will use these settings by default.

SWR with TypeScriptā€‹

SWR provides excellent TypeScript support:

tsx
import useSWR from 'swr'

interface User {
id: number
name: string
email: string
}

function Profile() {
// TypeScript knows that data will be of type User or undefined
const { data, error, isLoading } = useSWR<User>('/api/user', fetcher)

if (error) return <div>Failed to load</div>
if (isLoading) return <div>Loading...</div>

// TypeScript knows that data.name is accessible here
return <div>Hello {data.name}!</div>
}

Summaryā€‹

SWR is a powerful data fetching library that enhances Next.js applications with:

  • Automatic data revalidation on various conditions
  • Built-in caching mechanism
  • Deduplication of requests for improved performance
  • Easy handling of loading and error states
  • Support for dependent fetching, optimistic updates, and more

By using SWR, you can create reactive and realtime user interfaces without the complexity of manual state management, fetch requests, and caching logic.

Additional Resourcesā€‹

Exercisesā€‹

  1. Create a blog page that fetches and displays a list of posts using SWR
  2. Implement a "like" button that uses SWR's mutate function for optimistic updates
  3. Build a simple chat application that periodically polls for new messages
  4. Create a user settings page that fetches user data and allows updating profile information
  5. Implement a search feature with debounced API calls using SWR


If you spot any mistakes on this website, please let me know at [email protected]. Iā€™d greatly appreciate your feedback! :)