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 4 lines of logs:
// console.log(data, error, isValidating)undefined undefined true // => start fetchingundefined Error false // => end fetching, got an errorundefined Error true // => start retryingData undefined false // => end retrying, get the data
The state changes make sense. But that also means our component rendered 4 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 renderData // => 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.