How to use React Query to server side render in Next.js without code duplication
React Query supports prefetching, which allows you to make API calls to get your initial data on the server so that the first time your query is run (on the client or the server), it already has the data and never returns undefined
.
In its documentation React Query demonstrates how to use this prefetching technique to allow for server-rendering components. However, that example is intentionally simplified. Its purpose is to demonstrate the feature clearly, not to be particularly clean or avoid repetition.
The code in this article will be specific to Next.js and React Query’s hydration prefetch strategy, but the ideas presented here should be applicable to any application using React Query for SSR.
The starting pointSection titled: The starting point
The React Query SSR guide described above provides the following example for how to use prefetching:
That’s pretty simple, right? It is, but in my experience real world queries are more complicated than that.
- What happens when you need to prefetch those same data on multiple pages?
- Most queries end up having some kind of ID associated with them;
userId
,fileId
,documentId
, etc. That changes the structure of both the query key and thegetPosts
function. - Once that ID gets included in the query key, you now have the structure of that key duplicated on every page where you prefetch those data.
Grouping the prefetch with the useQuerySection titled: Grouping the prefetch with the useQuery
You can solve problems #1 & #2, and improve #3 by exporting an extra function in the same file that you create your useQuery
:
In my opinion, this is a big improvement over the previous solution!
- Query keys are no longer repeated throughout the entire app anywhere that needs to prefetch the query
- The
getServerSideProps()
function no longer needs to know about a bunch of React Query specific stuff likeQueryClient
,dehydrate
, and the special prop namedehydratedState
. It can instead just focus on the higher level work.
But it’s definitely not perfect:
- The query key (and query function) is still duplicated inside of the hook file.
- The query key also needs to be repeated inside of mutations where you want to optimistically update or cancel queries.
- It is no longer possible to prefetch multiple queries on the same page!
- That is almost certainly going to be a requirement for many applications
- Every query hook still has to know about the process of creating a
QueryClient
and also about the specialdehydratedState
prop name.
Isolate pieces of the querySection titled: Isolate pieces of the query
To solve for problems #4 and #5, let’s break out the the query key and query function that are shared between useGetPosts
and prefetchPosts
.
Boom, this code is now reasonably DRY! But we still can’t prefetch multiple queries on the same page and the code quickly becomes less DRY as more queries are added.
Final solutionSection titled: Final solution
To fix problem #6 and allow for multiple queries on the same page we need to somehow allow usage of the same QueryClient
between prefetches, because in the end we need all the data to be contained in a single dehydratedState
object that we get from the QueryClient
.
We could throw together a solution that instantiates the QueryClient
back in the page again and passes it around, but then we’re back to putting React Query specific code in the page itself.
What if instead we took advantage of the fact that everything we need to prefetch the query is now contained in object returned by getPostsQuery
? We can create a utility that accepts a list of those query “definitions” and shoves them all into the same QueryClient
.
That utility might look something like this:
Creating this utility also nicely solves problem #7 by letting it be the only place in our app that creates a QueryClient
(for the purposes of prefetching at least) and also the only place that knows about the dehydratedState
prop (aside from where it’s used for hydration).
That utility can then be used like this:
And that’s it! I hope this helps you and your team find the perfect SSR solution with React Query. If you come up with a way to improve it, let me know on Twitter!