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:
npm install swr
# or
yarn add swr
# or
pnpm add swr
Basic Usageā
Here's a simple example of using SWR to fetch data:
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
, andisLoading
- Based on these values, we can render different UI states
How SWR Worksā
SWR follows the "stale-while-revalidate" strategy:
- Return cached data (stale) immediately if available
- Send the fetch request to get fresh data
- 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.
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:
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:
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:
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:
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:
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:
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:
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:
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ā
- Create a blog page that fetches and displays a list of posts using SWR
- Implement a "like" button that uses SWR's mutate function for optimistic updates
- Build a simple chat application that periodically polls for new messages
- Create a user settings page that fetches user data and allows updating profile information
- 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! :)