Take your Next js + GraphQL + TypeScript Setup to the next level
Tags: react, graphql, nextjs, apollo
Published: 2023-02-28 13:11:14

**Integrating the full range of Next.js features with Apollo Client.**

*This is a continuation of the* [Next js + GraphQL + TypeScript Setup](https://ivanms1.hashnode.dev/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.**

![next.js and apollo cache](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cit5a5ha31bjbd0nkp9o.png)

### App Setup

To start I am going to use [this boilerplate](https://github.com/ivanms1/nextjs-graphql-typescript) that has already TypeScript and Apollo setup in Next js.

### Let's do this

1. Install the following dependencies.
    
    ```bash
    npm install lodash-es deepmerge
    ```
    
    We will need these libraries to mutate the Apollo cache later on.
    
2. Install the types for `lodash-es`
    
    ```typescript
    npm install --save-dev @types/lodash-es
    ```
    
3. Create a folder called `apollo` with an `index.ts` file inside.
    
4. Inside this file, create a function that initializes the apollo client.
    
    ```typescript
    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 `ssr` property 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 if `window` is defined or not.
    
5. Inside the same file create a function called `initializeApollo` with the following content.
    
    ```typescript
    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 `_apolloClient` with an existing `apolloClient` if it exists, or with a new instance of `createApolloClient()` 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 the `initialState` using `merge()`, and then restores the merged data into the cache. This allows the initial state to be used in any subsequent queries.
    
6. The last function in this file will be `addApolloState`
    
    ```typescript
    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 called `pageProps`.
    
    The function then checks if the `pageProps` object has a property called `props`. If it does, it adds the Apollo Client cache state to the `pageProps.props` object using the identifier `APOLLO_STATE_PROP_NAME`.
    
    Finally, the function returns the `pageProps` object with the Apollo Client cache state added to it. This function will be used in a Next.js page's `getStaticProps/getServerSideProps` function to inject the Apollo Client cache state.
    
7. Now let's create a hook that will help us get the Apollo client and pass it to our app. Create a folder called `hooks` with a file called `useApollo.ts`
    
    ```typescript
    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 `pageProps` using the identifier `APOLLO_STATE_PROP_NAME`. It then calls the `initializeApollo` function we created before to get either an existing or new instance of the Apollo client.
    
8. Now we are finally ready to connect the fully pre-populated client with our app. Inside `_app.tsx` add the following code.
    
    ```typescript
    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.ts` hooks to get the client and pass it to the `ApolloProvider`
    

*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.
```typescript
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.**

![rocket launch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/czsa7dojpfjhbwws9kc4.png)

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](https://github.com/ivanms1/nextjs-apollo-ssr)

Don't forget to like, share and star!

Thanks for reading and until the next time.