Next.js & Apollo GraphQL Performance Tuning: From Lists to Details

In some previous posts I've introduced Next.js and GraphQL a bit, and showed some tips on creating and paginating lists using these tools.

Our task today, is to navigate from a listing page to a detail page as fast and seamless as possible. We assume, that we have some data already for the detail page (like the name or a thumbnail), since the item we're about to view in more detail was already shown in a list.

Navigating from Lists to Details

Great thing about GraphQL types, while they may relate to each other and create complex trees, the entries of types can be stored as a flat, normalized data structure. The caching in Apollo client does exactly this, normalize your data, and provide a unique ID for every entry, whatever the type is. It will try to use any field of ID that looks like id or _id, so it's helpful to put any unique object ID in there.

If you have a different data schema, you can help the client to get these ID's if you want to, by passing a dataIdFromObject function to the Cache instance, but that's optional. See the module docs on Normalization for further info on this.

CacheRedirects

Now the exciting part! By finding entities in the cache by unique ID's and Type, we can redirect Queries to the cache and have them resolve immediately. Something like this, step-by-step:

  1. we start a Query
  2. the cacheRedirect tries to map the Query data in the cache
  3. if successful, it resolves the Query without touching the network

If the data is missing from the cache, the Query will run through the network, and works as usual.

The tricky part here, is that the fields of the redirected Query should be in the cache. You can't require a field from there that's not cached already - if this happens, the Query will miss cache and hit the network.

The Detail page

Lets get specific: here's the list of Starships, from the previous post, with a minor addition. Using next/link we're going to link the list item to the detail page of the same item:

gist:6810983cd90817fbd611be75c341cec9#StarshipList.jsx

So far so good, let's create a detail page for our data, listing a bunch of details of a Starship. First, this will be a regular page, with its own GraphQL Query for the data.

gist:6810983cd90817fbd611be75c341cec9#Starship_page.jsx

First thing that might occur to you is the Query for the detail page has parameters, and much more fields than the List page. Using the parameter $id, the GraphQL server can resolve the correct Starship item and send it back as a Query response. The fields are the details we would like to display on the Details page.

Below the Detail component itself is a static method, called getInitialProps. This is one of the best features of Next.js, where we can set up props for the page Component during Server-Side Rendering, or client-side navigation. In this case, we grab the ID from the query parameters, and set in to the props, so later we can use it on the Detail page.

Before creating the composed Component we need a small configuration for the GraphQL client:

  • notifyOnNetworkStatusChange set to true so we will be notified if teh client starts fetching data on the network
  • variables an object, where we pass the parameters for the Query, in our case, the ID

This Detail page works as it is now, but we have to wait for the Query to run, before we can show any data. To make the step from the List page to the Details page instant, we have to redirect part of the Query to the cache.

Redirect in practice

Simplest solution for this, is to have a separate Query with the same fields as the List had. We're going to do the following:

  • set up a small Query with just the name and the id - these are the fields queried for the list
  • leave the full Query as it is
  • combine the Query Components together, so when they resolve in order, they will incrementally render the detail page

gist:6810983cd90817fbd611be75c341cec9#Starship_page-redirect.jsx

Optimized Server-side rendering

Problem with this solution, that on first render it runs two queries, in series one after the another. To resolve that we can put a check if we are doing server-side rendering, or client side navigation - Next.js can help with this.

Even better solution, checking the cache if there is enough data in it, to start the incremental rendering of the Detail page! We can check the local GraphQL cache in the client, if there is enough data for a detail page, by using the readQuery or the readFragment methods - see the detailed documentation on these.

I've tried to explain all this on a few slides, see them here: