SWR + Dynamic Routes in Next.js

Hey folks!

If you recently worked with client side data fetching in Next.js, you probably heard of SWR. 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:

// 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:

// 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:

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:

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

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!

18