18
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 thequery
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?
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