Setting Up Nextjs 14 with Apollo Client and Codegen CLI

Gopal AdhikariGopal Adhikari
8 min read

Next.js is a powerful full stack framework for building React applications with features like server-side rendering and static site generation. When combined with Apollo Client, you can efficiently manage GraphQL queries and mutations in your application. This article will guide you through the process of setting up a Next.js 14 project with Apollo Client.

Prerequisites

Before you start, ensure you have the following installed on your machine:

  1. Node.js

  2. npm or yarn

  3. A Next.js application

This article assumes you already have a Next.js application set up and will not cover the steps to create one.

Setting up Apollo Client with Codegen-Cli

Since the Next.js app is already set up, run the following command to install the required dependencies:

npm install @apollo/client @apollo/experimental-nextjs-app-support graphql

The graphql package is a dependency of Apollo Client, used to parse and validate GraphQL queries. While we won't use graphql directly, it will be utilized by Apollo Client.

Now, create a file named client.ts inside your lib folder and place this code:

import { HttpLink } from "@apollo/client";
import {
    registerApolloClient,
    ApolloClient,
    InMemoryCache,
} from "@apollo/experimental-nextjs-app-support";

export const { getClient } = registerApolloClient(
    () =>
        new ApolloClient({
            cache: new InMemoryCache(),
            link: new HttpLink({
                uri: "place here your url here",
            }),
        })
);

We can further enhance our client.ts code for better debugging by implementing the logger provided by Apollo Client.

import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
// ... rest of your import

if (process.env.NODE_ENV !== "production") {
    loadDevMessages();
    loadErrorMessages();
}

// ... rest of your code

Next, run the following command to install the required packages for Codegen CLI:

npm install -D @graphql-codegen/cli @graphql-codegen/client-preset

Then initialize the Codegen CLI by running the following command:

npx graphql-code-generator init

Codegen CLI will ask you a few questions. You can answer them as follows:

  1. What type of application are you building? Choose Application built with React because this is a Next.js application built on top of React.

  2. Where is your schema? Provide the GraphQL playground URL.

  3. Where are the operations and fragments? Provide the local path of your GraphQL queries and mutations. For example, if your queries are in the root directory inside a graphql folder, you can use graphql/**/*.graphql to tell Codegen to track each GraphQL schema inside the graphql folder.

  4. Where to write the output? Provide the local path where the code generated by Codegen should be placed, for example, generated/graphql.ts.

  5. Do you want to generate an introspection file? If yes, it will generate a JSON file that tracks the overall GraphQL schemas. We will proceed with "no" for now, but we will cover the steps for generating an introspection file as an optional or additional task later.

  6. How to name a file config file? This will create a file that contains all the configuration for Codegen CLI. Write codegen.yml; the default is codegen.ts.

  7. What script in package.json should run the codegen? Choose the script to execute the codegen so it generates the code for us. The default is codegen. We will proceed with the default codegen script, which means our Codegen CLI gets triggered when we run the "npm run codegen" command.

list of question asked by codegen cli

After this execution is completed, this will create a codegen.yml file in the root directory. You can check that code and update to the following code

overwrite: true
schema: "https://gql.hashnode.com"
documents: "graphql/**/*.graphql"
generates:
  generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typed-document-node"

This is configured for TypeScript and creates a Document for our query and mutation. Also, check the package.json for the new "codegen" script, which would be similar to this:

 "codegen": "graphql-codegen --config codegen.yml"

Next, let's create a GraphQL query schema for fetching publications from Hashnode's GraphQL endpoint.

First, create a new folder named query inside your graphql folder. Then, create a new file named publication.graphql inside the query folder and place the following code:

query Publications($host: String, $first: Int!, $after: String) {
    publication(host: $host) {
        posts(first: $first, after: $after) {
            edges {
                node {
                    id
                    slug
                    title
                    coverImage {
                        url
                    }
                    content {
                        html
                    }
                }
            }
        }
    }
}

This query will fetch the id, title, slug, and content from the Hashnode API. Note the name given to this query, which is Publications; we will use it soon.

Now, run the npm run codegen command in the terminal. This will create a generated folder in the root directory, and inside that directory, there will be a graphql.ts file. This file contains the code generated by Codegen CLI for our use. While it may seem overwhelming at first, don't worry—we won't need to modify this file.

Next, update the client.ts for using the Hashnode API:

import { HttpLink } from "@apollo/client";
import {
    registerApolloClient,
    ApolloClient,
    InMemoryCache,
} from "@apollo/experimental-nextjs-app-support";
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";

if (process.env.NODE_ENV !== "production") {
    loadDevMessages();
    loadErrorMessages();
}

export const { getClient } = registerApolloClient(
    () =>
        new ApolloClient({
            cache: new InMemoryCache(),
            link: new HttpLink({
                uri: "https://gql.hashnode.com",
            }),
            headers: {
                Authorization: process.env.HASHNODE_ACCESS_TOKEN,
            },
        })
);

Place your Hashnode access token in the Authorization header for verification and update the URI to the Hashnode endpoint.

Create a graphql folder inside your lib folder and create a query.ts file inside the graphql folder and place the following code:

import {
    type PublicationsQuery,
    PublicationsDocument,
} from "../../../generated/graphql";
import { getClient } from "../client";

export const getPublications = async (first: number, after?: string | null) => {
    try {
        const { data } = await getClient().query<PublicationsQuery>({
            query: PublicationsDocument,
            variables: { host: "your hashnode blog url", first, after },
        });

        return data;
    } catch (error) {
        return null;
    }
};

Since we have noted that our query schema name is Publications, our document name is PublicationsDocument and its respective type is PublicationsQuery. If our GraphQL query's name was XYZ, then our document name would be XYZDocument and its respective type would be XYZQuery. So, we imported our PublicationsDocument and PublicationsQuery type from the graphql.ts file generated by Codegen and used it as shown above.

Now we can invoke this function where it's necessary. For example, we are going to invoke it in the homepage, i.e., app/page.tsx:

import { getPublications } from "@/lib/graphql/query";

export default async function Page() {
    const res = await getPublications(12);
    console.log(res)

    // rest of your code
}

This completes the setup of Next.js 14 with Apollo Client and Codegen CLI. You can now efficiently manage your GraphQL queries and mutations in your Next.js application.

Other important steps

Setting up introspection

To use introspection, install the following dependency:

npm i -D @graphql-codegen/introspection

Then, update the codegen.yml file as follows:

overwrite: true
schema: "https://gql.hashnode.com"
documents: "graphql/**/*.graphql"
generates:
  generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typed-document-node"
  generated/graphql.schema.json:
    plugins:
      - "introspection"

This configuration will create a graphql.schema.json file inside the generated folder, which will keep the record GraphQL queries and mutations.

Handling Cached Data in Apollo Client

Since Apollo Client uses the fetch API internally to retrieve data, we can leverage this feature to revalidate data at specific intervals. There are several ways to achieve this, depending on your requirements. Below, I outline four different methods to manage caching and revalidation in Apollo Client.

  1. Utilizing fetch options in HttpLink

You can customize the fetch behavior by using the fetch function inside HttpLink. In this example, we revalidate data every hour by passing a revalidate option to fetch.

// imports

export const { getClient } = registerApolloClient(
    () =>
        new ApolloClient({
            cache: new InMemoryCache(),
            link: new HttpLink({
                uri: "https://gql.hashnode.com",
                //  Revalidate data every hour
                fetch: async (uri, options) => {
                    const res = await fetch(uri, {
                        ...options,
                        next: { revalidate: 60 * 60 },
                    });
                    return res;
                },
            }),
        })
);
  1. Using fetchOptions in HttpLink

Another way is to configure fetchOptions directly within HttpLink, specifying the revalidation interval.

// imports

export const { getClient } = registerApolloClient(
    () =>
        new ApolloClient({
            cache: new InMemoryCache(),
            link: new HttpLink({
                uri: "https://gql.hashnode.com",
                //  Revalidate data every hour
                fetchOptions: { 
                    next: {
                        revalidate: 60 * 60,
                    },
                },
            }),
        })
);
  1. Using defaultOptions for Global Revalidation

You can set revalidation behavior globally for all queries by using defaultOptions and defining fetchOptions at the query level. In this example, data is revalidated every three hours.

// imports

export const { getClient } = registerApolloClient(
    () =>
        new ApolloClient({
            cache: new InMemoryCache(),
            link: new HttpLink({
                uri: "https://gql.hashnode.com",
            }),
            //  Revalidate data every hour
            defaultOptions: {
                query: {
                    context: {
                        fetchOptions: {
                            next: {
                                revalidate: 60 * 60 * 3, // 3 hours
                            },
                        },
                    },
                },
            },
        })
);
  1. Using context in Individual Requests

For fine-grained control, you can also set caching behavior at the individual request level by providing context within a query. In this example, caching is disabled for the request.

// imports

export const getDraft = async (id?: string) => {
    const { data } = await getClient().query<DraftQuery>({
        query: DraftDocument,
        variables: { id },
        context: {
            fetchOptions: {
                cache: "no-store", // no caching

                //  or you can pass
                // next: {
                //     revalidate: 60 * 60,
                // },
            },
        },
    });
    return data;
};

You only need to configure one of the caching methods described above based on your use case. Don't combine multiple methods at the same time, as each approach handles caching and revalidation in a different way. Pick the one that best fits your scenario, whether it’s global caching, request-level control, or customizing the fetch function.

In Apollo Client, the cache is highly dependent on the presence of a unique identifier (id) for each field. Even if you don't think you need an id in some queries, it’s a good practice to always fetch the id field wherever possible. Failing to do so can result in unexpected caching behavior, such as stale or missing data. The id helps Apollo efficiently track and manage your cached data across queries and mutations.

Summary

This article provides a comprehensive guide to setting up a Next.js 14 project with Apollo Client and GraphQL Codegen. It covers installing necessary dependencies, configuring Apollo Client, using Codegen for type-safe GraphQL queries, setting up a sample query against Hashnode's API, and handling cached data with various revalidation strategies. Prerequisites include having Node.js, npm or yarn, and a pre-existing Next.js application. Additionally, it includes optional steps for setting up introspection to maintain schema records.

0
Subscribe to my newsletter

Read articles from Gopal Adhikari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Gopal Adhikari
Gopal Adhikari

I am a web developer with interest in mobile app development and cloud.