Server-side rendering (SSR) using Apollo and Next.js

I started this article because I struggled to find a way to combine both server-side rendering techniques for Apollo and Next.js. Both of them have their own way with their own philosophy:

Apollo:

Has that special method called getDataFromTreethat finds in your page tree all components that use the hook useQuery. They then execute all queries at once to allow you to send your page prefilled with data to the browser.

Next.js:

Has the flag method getServerSidePropsthat lets the lib know you want that specific page to be server-side rendered whilst providing the page component with the data (prefilled in its props argument)

The headache starts when you try to combine those two approaches: the Next.js approach wants you to rely on the component’s props argument to access the data on the server-side, but since you’re using Appollo, your code relies on useQuery on the client-side. On the other hand, Apollo doesn’t provide an easy way to inject data in its useQuery hook so that you could say: “if I’m server-side, don’t query GraphQL, use this data I grabbed for you”.

Fortunately, after many people asking this question, next.js provided an example of how to use apollo without violating the next.js architecture philosophy. Reading the example is not straightforward at all, I will explain what is happening behind the scene here.

Apollo Cache:

Apollo’s useQuery method doesn't necessarily execute the query on the server each time: it can get the result from the cache. That’s not the browser cache, it is a cache specific to Apollo. So if you manage to execute your query before useQuery is called, you can avoid fetching data from the server since it will read from the cache. This means we don’t have to pass our data as arguments, we can simply rely on Apollo’s cache.

We’ve talked about it above, getServerSidePropsis called by Next.js before the page is rendered since with it, it detects that a page is a server-side one. That means it is called before useQuery. Good news! We know where we should execute all queries needed by our page.

Show me the way:

So let’s jump to code and see if it is that simple. Here’s a page that I would like to render server-side. So bad news first: getDataFromTreedidn’t work for me. That made it clear to me why the example provided by next.js doesn’t use it too :-). I filed a bug to Apollo about that.
The good news is that in most cases it is quite simple to workaround. You have to manually call all the queries that your page triggers

ssrPage.js :

I’m initializing the apollo client at the very first line since on the server-side it is better to create a new client for each request to avoid the cache of a client being revealed to another one, which would be a serious security issue.

Then I’m simply executing my GraphQL queries through the client to fill Apollo’s cache with the data the page needs.

Then, addApolloState is called to add the cached content to the component properties. Next.js will now call his special page located at pages/_app.js, and he needs to have a prefilled client with data. We access the client through a hook called useApollo that’s purpose is to return the same instance of apollo client if it was already created. The hook takes component props with the cache inside as an argument. Please note, you can initiate it with other properties as I did with serverSideTranslations call to prepare i18n resources for example.

At this point, you must wonder what addApolloState and useApollo look like. Here it is nothing special, just utility methods to extract specific code to its dedicated file.

client.js :

client.js

You can check this article to understand the different fetch policies of Apollo.

So, useApollo only gets the already created client instance and prefills it with apollo state from props if available. We make that state available by calling addApolloState and returning it from getServerSideProps.

Now your code should be SSR, you can verify it either by disabling javascript in your browser or simply by checking that useQuery returns the loading value as false at first render. I’ll paste my _app.js just for reference. All you need to have is a call to useApollo

You might wonder why we couldn’t just call the client.query methods in getServerSidePropsand use the client cache as it should be filled: why do we serialize the cache, pass it the props param of the component so that _app.js can fill the cache with it. Isn’t the cache already filled?
That’s a good question, the answer is that the instance of client is different when next calls _app.js so the cache have disappeared with the first instance. The next question is “why is it a different instance?”. And I still don’t have the answer for that. You coud leave the answer to that in the comments, or any other questions. I will be happy to update this article so that anyone can benefit.

Photo by Diggity Marketing on Unsplash

--

--

Zied Hamdi - https://github.com/ziedHamdi

Founder and CEO of WeAlly.org, I love helping people. I created WeAlly to make people's needs and complaints louder, increasing their network and influence.