Prefetching Data in a Next.js Application with SWR
Tags: swr, nextjs, react, prefetch
Published: 2020-01-22 22:22:44

---
title: Prefetching Data in a Next.js Application with SWR
description: Optimizing the performance of a Next.js using SWR and data prefetching.
tags: swr, nextjs, react, prefetch
published: true
canonical_url: https://sergiodxa.com/articles/next-swr-prefetch/
---

_Originally published at https://sergiodxa.com/articles/next-swr-prefetch/_

Next.js comes with an amazing performance optimization where it will do code splitting of each page but if your page link to another one it will prefetch the JavaScript bundle as low priority, this way once the user navigates to another page it will, probably, already have the bundle of the new page and render it immediately, if the page is not using `getInitialProps`.

This works amazingly great and makes navigation super-fast, except you don't get any data prefetching benefit, your new page will render the loading state and then render with data once the requests to the API resolved successfully.

But the key thing here is that we, as a developer, may probably know which data the user will need on each page, or at least most of it, so it's possible to fetch it before the user navigates to another page.

SWR it's another great library, from the same team doing Next.js, which let use do remote data-fetching way easier, one of the best part of it is that while each call of SWR will have its own copy of the data it also has an external cache, if a new call of SWR happens it will first check in the cache to get the data and then revalidate against the API, to be sure we always have the correct data.

This cache's also updatable from the outside using a simple function called `mutate` which SWR gives us. This is great since we could call this function and then once a React component is rendered using SWR it will already have the data in the cache.

## Running Demo

> This is the final project running in CodeSandbox

{% codesandbox prefetching-data-in-a-nextjs-application-with-swr-0mvv7 %}

## Defining the Project

Let's say our application will have a navigation bar, this is super common, imagine we have three links.

- Home
- My Profile
- Users

The **Home** page will show some static data, **My Profile** will render the current user profile page and **Users** will render the list of users.

So we could add this navigation bar in our `pages/_app.js` to ensure it's rendered in every page and it's not rerendered between navigation so we could keep states there if we needed it (we will not in our example), so let's imagine this implemented.

```js
export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Navigation>
        <NavItem label="Home" href="/" />
        <NavItem label="My Profile" href="/my-profile" />
        <NavItem label="Users" href="/users" />
      </Navigation>
      <Main>
        <Component {...pageProps} />
      </Main>
    </Layout>
  );
}
```

It could be something like that, `Layout` render a div with a CSS Grid to position the `Navigation` and the `Main` components in the correct places.

Now if the user clicks on **Home** we now will not show any dynamic data, so we don't care about that link, we could let Next.js prefetch the JS bundle and call it a day.

But **My Profile** and **Users** will need dynamic data from the API.

```js
export default function MyProfile() {
  const currentUser = useCurrentUser();
  return <h2>{currentUser.displayName}</h2>;
}
```

That could be the `MyProfile` page, we call a `useCurrentUser` hook which will call `useSWR` internally to get the currently logged-in user.

```js
export default function Users() {
  const users = useUsers();
  return (
    <section>
      <header>
        <h2>Users</h2>
      </header>
      {users.map(user => (
        <article key={user.id}>
          <h3>{user.displayName}</h3>
        </article>
      ))}
    </section>
  );
}
```

As in `MyProfile` the custom hook `useUsers` will call `useSWR` internally to get the list of users.

## Applying the Optimization

Now let's define our `NavItem` component, right now based on our usage it may work something like this.

```js
export default function NavItem({ href, label }) {
  return (
    <Link href={href}>
      <a>{label}</a>
    </Link>
  );
}
```

Let's add the prefetch, imagine we could pass a `prepare` function to NavItem where we could call functions to fetch the data and mutate the SWR cache.

```js
<Navigation>
  <NavItem label="Home" href="/" />
  <NavItem
    label="My Profile"
    href="/my-profile"
    prepare={() => getCurrentUser()}
  />
  <NavItem label="Users" href="/users" prepare={() => getUsers()} />
</Navigation>
```

Let's make it work updating our `NavItem` implementation.

```js
function noop() {} // a function that does nothing in case we didn't pass one
export default function NavItem({ href, label, prepare = noop }) {
  return (
    <Link href={href}>
      <a onMouseEnter={() => prepare}>{label}</a>
    </Link>
  );
}
```

Now if the user mouse enter the link, aka the user hover the link, we will call our `prepare` function, we could do this because if the user hovers the link it may want to click it, so we trigger the fetch of the data, once the user clicks it may have already fetched it and updated SWR cache if the user never clicks we only prefetched data and cache it for nothing but didn't lose anything.

Now let's implement `getUsers` and `getCurrentUser` functions.

```js
export function fetcher(path) {
  return fetch(path).then(res => res.json());
}

export function fetchAndCache(key) {
  const request = fetcher(key);
  mutate(key, request, false);
  return request;
}

export function getCurrentUser() {
  return fetchAndCache("/api/users/current");
}

export function getUsers() {
  return fetchAndCache("/api/users");
}
```

The `fetcher` function triggers the `fetch` and parses the response as JSON.

The `fetchAndCache` function will call `fetcher`, keep the promise, not the result since we are not awaiting it or calling `.then`, and pass the `key`, our URL, to `mutate` along with the request promise, the `false` as the third argument will tell SWR to not revalidate the data against the backend, we don't need it because we just fetched it so we will it to don't do that.

Lastly `getCurrentUser` and `getUsers` are wrappers around `fetchAndCache` to specify a certain `key` (URL).

> Note: We call the URL key because SWR uses the first argument as cache key too, it doesn't necessarily need to be a valid URL, e.g. we could users `users` or `users/current` and prepend `/api/` in the fetcher.

With all of this once we hover over **My Profile** and **Users** it will trigger the fetch now, if we navigate to it we will see the data rendered right away without waiting, SWR will still fetch it again to revalidate once `useSWR` is called to be sure we always have the correct data.

## Final Words

As you could see adding a simple function call before the user starts a page navigation it could help us increase the perceived performance of our application, we could continue improving this adding checks to ensure we are not prefetching data if the users are on a low-speed connection or using mobile data which could help him save data and only load what it really needs.