SWR + Dynamic Routes in Next.js
Tags: nextjs, tutorial, webdev, react
Published: 2021-07-20 13:16:33

Hey folks!

If you recently worked with client side data fetching in Next.js, you probably heard of [SWR](https://swr.vercel.app/). It comes with `useSWR`, a React hook that makes all the complicated stuff in client side data fetching (caching, revalidation, focus tracking etc.) easy as pie.

You can implement it with just a few lines of code:

```javascript
// Import the hook
import useSWR from 'swr'

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

function Profile() {
  // Use the hook to fetch your data
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return <div>hello {data.name}!</div>
}
```

Easy, right? Well, it definitely is when you try to fetch an endpoint with no query parameters, like `/api/user`. But when you try to pass a dynamic route parameter to your `useSWR` hook, things can get a little bit tricky. I recently spent some time figuring out a solution for this, so I thought I should share my solution.

Let's say we have a dynamic user route under `/pages/user/[id].js`, which should show a user profile based on the ID we pass as a route parameter.

The code to access that ID parameter would look like this:

```javascript
// Import the useRouter hook from Next.js
import { useRouter } from 'next/router'

function Profile() {
  // Use the useRouter hook
  const router = useRouter()

  // Grab our ID parameter
  const { id } = router.query

  return <div>user id: {id}</div> 
}
```

If you open that page with a random ID (`http://localhost:3000/user/42` i.e), you should see the ID on the rendered page (`user id: 42`). Now, instead of just rendering that ID, let's fetch the user related to that ID from our API endpoint and render a profile page.

When I tried to do that, I thought I could just pass the ID parameter to the `useSWR` hook and voilá – a beautiful profile page. The code looked like that:

```javascript
import useSWR from 'swr'
import { useRouter } from 'next/router'

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

function Profile() {
  const router = useRouter()
  const { id } = router.query

  const { data, error } = useSWR(`/api/user/${id}`, fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return <div>hello {data.name}!</div>
``` 

But then the error messages came in – something obviously didn't work, my component just won't fetch the user. What happened here? When I had a look into the network tab, I noticed that the ID parameter wasn't passed to the fetch call – instead it said `undefined`. But why? The ID was clearly there, so what the heck happened here?

The answer is in the [Next.js Docs](https://nextjs.org/docs/routing/dynamic-routes#caveats):

> Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e `query` will be an empty object (`{}`). After hydration, Next.js will trigger an update to your application to provide the route parameters in the `query` object.

Since I didn't use `getServerSideProps` or `getStaticProps` on that page, Next turned on Automatic Static Optimization for it – which means the dynamic parameters from `router.query` are not available until the hydration process has finished. Before, `query` is just an empty object – that's why the network tab said `undefined`.

So how can we tell `useSWR` to wait until our dynamic route parameter is ready?

## TL;DR
```javascript
import useSWR from 'swr'
import { useRouter } from 'next/router'

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

function Profile() {
  const router = useRouter()
  const { id } = router.query
  
  // Use a ternary operator to only fetch the data when the ID isn't undefined
  const { data, error } = useSWR(id ? `/api/user/${id}` : null, fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return <div>hello {data.name}!</div>
``` 

This way our page now initially renders `Loading...`, and as soon as the hydration process has finished it fetches the user data and renders the profile.

I hope this little explanation could help you!