Integrating the full range of Next.js features with Apollo Client.
This is a continuation of the Next js + GraphQL + TypeScript Setup article
Welcome back to our blog continuation! In this post, we'll show you how to take advantage of Next.js capabilities in combination with Apollo and GraphQL. Our focus will be on integrating Apollo Client with some of Next.js most essential features, including server-side rendering (SSR), static site generation (SSG), and incremental static regeneration (ISR). However, before we dive into the specifics, let's take a quick look at what each of these features involves.
What is SSR?
Server-Side Rendering (SSR) is a technique that allows you to pre-render your website's pages on the server and serve them to the client as HTML files. With SSR, the initial page load is faster because the server sends the HTML file with the rendered content, rather than sending a blank page that needs to be filled with content after the JavaScript loads.
In addition to improving initial page load times, SSR can also improve SEO, as search engines can easily crawl and index your pre-rendered pages. SSR can also help to optimize performance for users with slow internet connections or less powerful devices.
SSR can be implemented in many ways, but Next.js provides a built-in API that simplifies the process. By using Next.js, you can easily enable SSR for your React components, allowing you to take advantage of the benefits of SSR without needing to write a lot of server-side code.
What about SSG and ISR?
Static site generation (SSG) is a method of building websites that pre-renders all pages of a website at build time, and serves those pre-rendered pages to users, as opposed to generating pages on demand. While SSG is a fast and scalable approach to building websites, it has the limitation that every time you want to update any content, you need to rebuild the entire site. Incremental Static Regeneration (ISR) is a feature of Next.js that addresses this limitation by allowing you to update specific pages of your website without rebuilding the entire site.
It's important to note that SSG and ISR offer the benefits of SSR while also being more performant.
In the next sections, we will look at how you can combine SSR and ISR with GraphQL and Apollo Client to take your Next.js app to the next level.
Apollo's cache and SSR/SSG/ISR
Apollo Client provides a powerful cache system that allows you to manage the state of your application and access data quickly without having to make a network request. When you use Apollo Client to make a GraphQL query, the results are automatically stored in the cache, making subsequent requests for the same data much faster.
To achieve SSR/SSG/ISR in a Next.js app, you can make a network request on the server using Apollo Client and pre-populate the cache with the data. This allows the data to be immediately available on the client side, without the need for additional network requests.
By using the cache in combination with SSR/SSG/ISR, you can greatly improve the performance and user experience of your app, ensuring that data is always available quickly and reliably.
Here is a little diagram.
App Setup
To start I am going to use this boilerplate that has already TypeScript and Apollo setup in Next js.
Let's do this
-
Install the following dependencies.
npm install lodash-es deepmergeWe will need these libraries to mutate the Apollo cache later on.
-
Install the types for
lodash-es
npm install --save-dev @types/lodash-es Create a folder called
apollowith anindex.tsfile inside.-
Inside this file, create a function that initializes the apollo client.
import { ApolloClient, InMemoryCache } from '@apollo/client'; const COUNTRIES_API = 'https://countries.trevorblades.com'; function createApolloClient() { return new ApolloClient({ ssrMode: typeof window === 'undefined', uri: COUNTRIES_API, cache: new InMemoryCache(), }); }This should look familiar, now it's just abstracted into a function. Here we create an instance of an Apollo Client, the
ssrproperty is very important here, it determines whether the client is being used in server-side rendering mode (true) or client-side rendering mode (false). All of this is based on ifwindowis defined or not. -
Inside the same file create a function called
initializeApollowith the following content.
import merge from 'deepmerge'; import isEqual from 'lodash-es/isEqual'; import { ApolloClient, InMemoryCache } from '@apollo/client'; let apolloClient: ApolloClient<NormalizedCacheObject> | null; function createApolloClient() { //... } export function initializeApollo(initialState?: any) { const _apolloClient = apolloClient ?? createApolloClient(); if (initialState) { const existingCache = _apolloClient.cache.extract(); const data = merge(initialState, existingCache, { arrayMerge: (destinationArray, sourceArray) => [ ...sourceArray, ...destinationArray.filter((d) => sourceArray.every((s) => !isEqual(d, s)) ), ], }); _apolloClient.cache.restore(data); } if (typeof window === 'undefined') { return _apolloClient; } if (!apolloClient) { apolloClient = _apolloClient; } return _apolloClient; }This function initializes the Apollo client. It creates a new variable called
_apolloClientwith an existingapolloClientif it exists, or with a new instance ofcreateApolloClient()if it doesn't. This ensures that there is only one instance of the client throughout the application. If there is an initial state, it extracts the existing cache from_apolloClient, merges it with theinitialStateusingmerge(), and then restores the merged data into the cache. This allows the initial state to be used in any subsequent queries. -
The last function in this file will be
addApolloState
import merge from 'deepmerge'; import isEqual from 'lodash/isEqual'; import { ApolloClient, InMemoryCache } from '@apollo/client'; let apolloClient: ApolloClient<NormalizedCacheObject> | null; export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'; function createApolloClient() { // ... } export function initializeApollo(initialState?: any) { // ... } export function addApolloState( client: ApolloClient<NormalizedCacheObject>, pageProps: any ) { if (pageProps?.props) { pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract(); } return pageProps; }This utility function, called
addApolloState, takes two parameters: an instance of an of Apollo client and an object containing props passed to a Next.js page calledpageProps.The function then checks if the
pagePropsobject has a property calledprops. If it does, it adds the Apollo Client cache state to thepageProps.propsobject using the identifierAPOLLO_STATE_PROP_NAME.Finally, the function returns the
pagePropsobject with the Apollo Client cache state added to it. This function will be used in a Next.js page'sgetStaticProps/getServerSidePropsfunction to inject the Apollo Client cache state. -
Now let's create a hook that will help us get the Apollo client and pass it to our app. Create a folder called
hookswith a file calleduseApollo.ts
import { useMemo } from 'react'; import { APOLLO_STATE_PROP_NAME, initializeApollo } from '../apollo'; function useApollo(pageProps: any) { const state = pageProps[APOLLO_STATE_PROP_NAME]; const client = useMemo(() => initializeApollo(state), [state]); return client; } export default useApollo;The hook first retrieves the Apollo Client cache state from
pagePropsusing the identifierAPOLLO_STATE_PROP_NAME. It then calls theinitializeApollofunction we created before to get either an existing or new instance of the Apollo client. -
Now we are finally ready to connect the fully pre-populated client with our app. Inside
_app.tsxadd the following code.
import type { AppProps } from 'next/app'; import { ApolloProvider } from '@apollo/client'; import useApollo from '../hooks/useApollo'; import '../styles/globals.css'; function MyApp({ Component, pageProps }: AppProps) { const client = useApollo(pageProps); return ( <ApolloProvider client={client}> <Component {...pageProps} /> </ApolloProvider> ); } export default MyApp;Now instead of creating the client instance here, we use the
useApollo.tshooks to get the client and pass it to theApolloProvider
With this change, our Next.js app is now fully connected to our Apollo Client instance and can use GraphQL queries to fetch and display data.
Time to test
Now let's populate the countries query via getStaticProps.
Inside pages/index.tsx add the following code.
import Head from 'next/head';
import { useQuery } from '@apollo/client';
import type { GetStaticProps } from 'next';
import QUERY_COUNTRIES from './queryCountries.graphql';
import { addApolloState, initializeApollo } from '../apollo';
import styles from '../styles/Home.module.css';
export default function Home() {
// ...
}
export const getStaticProps: GetStaticProps = async (ctx) => {
const client = initializeApollo();
await client.query({
query: QUERY_COUNTRIES
});
return addApolloState(client, {
props: {},
});
};
So here we initialize the client, make the query and pass the client, with a populated cache, to the addApolloState function along with the page props. We know that this function will add the apollo cache to the pageProps. This ensures that the data is pre-populated in the cache when the page is rendered on the client side.
Now, if you inspect the network tab, you should not see any loading and no requests being made by the useQuery hook on the client, as the data is already available in the cache.
In this example, we utilize the getStaticProps function to generate a static page, which enables SSG/ISR functionality. This approach can also be employed within the getServerSideProps function to provide us with SSR capabilities.
Conclusion
And that's about it!
We have now integrated the capabilities of Next.js with Apollo Client to take our app to the next level.
You can find the complete code here
Don't forget to like, share and star!
Thanks for reading and until the next time.


Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.