Skip to content

Performance

SWR provides critical functionality in all kinds of web apps, so performance is a top priority.

SWR’s built-in caching and deduplication skips unnecessary network requests, but the performance of the useSWR hook itself still matters. In a complex app, there could be hundreds of useSWR calls in a single page render.

SWR ensures that your app has:

  • no unnecessary requests
  • no unnecessary re-renders
  • no unnecessary code imported

without any code changes from you.

Deduplication

It’s very common to reuse SWR hooks in your app. For example, an app that renders the current user’s avatar 5 times:

function useUser () {
return useSWR('/api/user', fetcher)
}
function Avatar () {
const { data, error } = useUser()
if (error) return <Error />
if (!data) return <Spinner />
return <img src={data.avatar_url} />
}
function App () {
return <>
<Avatar />
<Avatar />
<Avatar />
<Avatar />
<Avatar />
</>
}

Each <Avatar> component has a useSWR hook inside. Since they have the same SWR key and are rendered at the almost same time, only 1 network request will be made.

You can reuse your data hooks (like useUser in the example above) everywhere, without worrying about performance or duplicated requests.

There is also a dedupingInterval option for overriding the default deduplication interval.

Deep Comparison

SWR deep compares data changes by default. If the data value isn’t changed, a re-render will not be triggered.

You can also customize the comparison function via the compare option if you want to change the behavior. For example, some API responses return a server timestamp that you might want to exclude from the data diff.

Dependency Collection

useSWR returns 3 stateful values: data, error and isValidating, each one can be updated independently. For example, if we print those values within a full data-fetching lifecycle, it will be something like this:

function App () {
const { data, error, isValidating } = useSWR('/api', fetcher)
console.log(data, error, isValidating)
return null
}

In the worst case (the first request failed, then the retry was successful), you will see 5 lines of logs:

// console.log(data, error, isValidating)
undefined undefined false // => hydration / initial render
undefined undefined true // => start fetching
undefined Error false // => end fetching, got an error
undefined Error true // => start retrying
Data undefined false // => end retrying, get the data

The state changes make sense. But that also means our component rendered 5 times.

If we change our component to only use data:

function App () {
const { data } = useSWR('/api', fetcher)
console.log(data)
return null
}

The magic happens — there are only 2 re-renders now:

// console.log(data)
undefined // => hydration / initial render
Data // => end retrying, get the data

The exact same process has happened internally, there was an error from the first request, then we got the data from the retry. However, SWR only updates the states that are used by the component, which is only data now.

If you are not always using all these 3 states, you are already benefitting from this feature. At Vercel, this optimization results in ~60% fewer re-renders.

Tree Shaking

The SWR package is tree-shakeable and side-effect free. That means if you are only importing the core useSWR API, unused APIs like useSWRInfinite won't be bundled in your application.