March 14, 2023

How to use Apollo Client with Next.js 13

Patrick Arminio

Next.js 13 was probably one of the most awaited releases of Next.js It brought us the app directory, with support for layouts, React Server Components and more!

The most interesting feature is the introduction of React Server Components. To put it simply Server components allow you to run data fetching on the server removing the need of client side fetches in most cases!

That brings us a question, can you use Apollo Client with React Server Components? The answer is yes, but there’s nuances to that, especially when using Next.js 13!

Feel free to skip over to the TLDR; of this post if you want a summary on how to use Apollo with Next.js!

In this blog post we’ll learn how to use Apollo Client in server components, how Next.js cache works and when you should use Apollo Client in server components and when to use them in Client Components.

Let’s start with learning how to do data fetching inside server components. I’ve prepared a simple GraphQL API that returns the current time. We’ll use this API to show how the caching works!

An example of data fetching inside server components

Here’s a basic example on how you would an GraphQL query using fetch inside server components:

export default async function Page() {
  const data = await fetch(
    "https://main--time-pav6zq.apollographos.net/graphql",
    {
      method: "POST",
      body: JSON.stringify({
        query: '{ now(id: "1") }',
      }),
      headers: {
        "Content-Type": "application/json",
      },
    }
  ).then((res) => res.json());

  return <main>{data.now}</main>;
}

One thing you might notice here is that we are creating an async component, this is enabled by Server Components and it means we can await the result of our data fetching inside our component! I think this is pretty neat as it makes dealing with data fetching so much easier!

The fetch call above is a bit verbose, so let’s replace that with Apollo Client! As I mentioned above, we can still use Apollo Client in Server Components, but there’s one main gotcha. In Server Components, you’re not allowed to use any hook. So, useState, useContext and more are forbidden, this also includes ApolloProvider (which uses the context) and useQuery.

But that’s not a problem, we can still use the client directly! Let’s see an example of this, let’s start by setting up a function that allows us to get the client.

import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";

let client: ApolloClient<any> | null = null;

export const getClient = () => {
  // create a new client if there's no existing one
  // or if we are running on the server.
  if (!client || typeof window === "undefined") {
    client = new ApolloClient({
      link: new HttpLink({
        uri: "https://main--time-pav6zq.apollographos.net/graphql",
      }),
      cache: new InMemoryCache(),
    });
  }

  return client;
};

Before using this in a client component, let me explain what it does and why! This getClient function gets or creates an Apollo Client, with the caveat that it always returns a new client when running on the server side (if window is undefined) and it reuses the existing one on the client side.

Apollo Client’s cache is designed with a single user in mind, so I recommend that your Next.js server instantiates a new cache for each SSR request, rather than reusing the same long-lived instance for multiple users’ data.

Now that we have a way to fetch the client, we can use it directly in our Server Component, like so:

// app/page.tsx
import { getClient, clientNoCache } from "@/lib/client";

import { gql } from "@apollo/client";

const query = gql`query Now {
    now(id: "1")
}`;

export default async function Page() {
  const client = getClient();
  const { data } = await client.query({ query });

  return <main>{data.now}</main>;
}

That’s much better! By using Apollo Client we can also re-use all the tooling built around it, like codegen for example.

Let’s see how Next.js handles http requests when using Server Components.

Next.js Server Components cache

When using Server Components, Next.js overrides all the fetch requests being done there and adds caching to them.

Next.js cache is a document based cache, which is different from Apollo Client’s normalized cache. If you want to know more about how Apollo Cache works head over to this blog post.

 Let’s go back to our initial example:

export default async function Page() {
  const data = await fetch(
    "https://main--time-pav6zq.apollographos.net/graphql",
    {
      method: "POST",
      body: JSON.stringify({
        query: '{ now(id: "1") }',
      }),
      headers: {
        "Content-Type": "application/json",
      },
    }
  ).then((res) => res.json());

  return <main>{data.now}</main>;
}

If we deploy this to Vercel and hit our production page a few times, we’ll see the same value over and over again.

This is Next.js’ default caching strategy, if we don’t pass any caching option to our fetch requests or to the page, then Next.js will cache the results when building the page.

Let’s see how we can tell next to invalidate this fetch every 10 seconds:

export default async function Page() {
  const data = await fetch(
    "https://main--time-pav6zq.apollographos.net/graphql",
    {
      method: "POST",
      body: JSON.stringify({
        query: '{ now(id: "1") }',
      }),
      headers: {
        "Content-Type": "application/json",
      },
      next: { revalidate: 10 }
    }
  ).then((res) => res.json());

  return <main>{data.now}</main>;
}

Here you can see we passed { next: { revalidate: 10 } } to our fetch. This is an extension to the standard fetch API from Node.js, and it tells Next.js to revalidate this specific request every 10 seconds.

There are quite a few options on caching, read this Next.js’s docs to know more about them: https://beta.nextjs.org/docs/data-fetching/caching

Next.js cache and Apollo

Since Next.js patches all the fetch requests, this means that requests done with Apollo Client are also affected.

And, if you’re wondering, yes, it doesn’t matter whether requests are GET or POST, Next.js caches all of them!

We updated our client to not do any caching on the server side, let’s try our request again:

// app/page.tsx
import { getClient } from "@/lib/client";

import { gql } from "@apollo/client";

const query = gql`query Now {
    now(id: "1")
}`;

export default async function Page() {
  const client = getClient();
  const { data } = await client.query({ query });

  return <main>{data.now}</main>;
}

If we deploy this to Vercel and hit this page, we’ll see the same behavior as the page with the plain fetch request. The data is cached at build time!

Revalidating GraphQL requests

We wouldn’t want all our GraphQL requests to be cached forever, fortunately Apollo Client allows us to pass fetch options to our queries, let’s see that in action by updating our page to revalidate that GraphQL query every 5 seconds:

// app/page.tsx
import { getClient } from "@/lib/client";

import { gql } from "@apollo/client";

const query = gql`query Now {
    now(id: "1")
}`;

export default async function Page() {
  const client = getClient();
  const { data } = await client.query({ 
    query,
    context: {
      fetchOptions: {
        next: { revalidate: 5 },
      },
    },
  });

  return <main>{data.now}</main>;
}

Here, we used the context argument to pass fetchOptions to our client (HTTPLink will read them and pass them to the underlying fetch call).

Additionally, we can also use Segment Options to specify the caching options for a particular page, the example above could also be done like this:

// app/page.tsx
import { getClient } from "@/lib/client";

import { gql } from "@apollo/client";

//           👇
export const revalidate = 5;

const query = gql`query Now {
    now(id: "1")
}`;

export default async function Page() {
  const client = getClient();
  const { data } = await client.query({ query });

  return <main>{data.now}</main>;
}

Now we know how the caching works in Next.js and Apollo and how to change it based on user preferences. Before doing a recap of this post, I wanted to mention how you can still use Apollo Client with useClient and useMutation.

Client components

As I mentioned before, all pages inside Next.js’ app directory are Server Components by default, so we can’t use anything that uses context, state and so on. Fortunately there’s a directive that tells React that a component is a client component, which means this component will be rendered both on server side (when using a SSR framework like Next.js) and on the client side. This directive is called "use client"  and it works like this:

"use client";

import { getClient } from "@/lib/client";

import { gql } from "@apollo/client";

const query = gql`query Now {
    now(id: "1")
}`;

export default function Page() {
  const client = getClient();
  const { loading, data } = useQuery(query, { client });
  if (loading) { return <p>Loading</p>; }

  return <main>{data.now}</main>;
}

By adding "use client” and passing the Apollo Client directly to useQuery we can keep using Apollo Client in our hooks 

This is especially useful if you need to run mutations, since they should be run from on client side.

TLDR;

Apollo Provider doesn’t work in Server Components due to hooks not allowed to run on the server. In Server Components we can use the Apollo Client directly. In Client Components we can pass the client directly to useQuery or useMutation.

To make it easier to work with cached data and prevent potential data issues, we always create a new Apollo Client instance on the server side.

Here’s a full snippet for our client setup:

import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";

let client: ApolloClient<any> | null = null;

export const getClient = () => {
  // create a new client if there's no existing one
  // or if we are running on the server.
  if (!client || typeof window === "undefined") {
    client = new ApolloClient({
      link: new HttpLink({
        uri: "https://main--time-pav6zq.apollographos.net/graphql",
      }),
      cache: new InMemoryCache(),
    });
  }

  return client;
};

And this is how we can use the client inside a Server Component:

// app/page.tsx
import { getClient } from "@/lib/client";

import { gql } from "@apollo/client";

export const revalidate = 5;
const query = gql`query Now {
    now(id: "1")
}`;

export default async function Page() {
  const client = getClient();
  const { data } = await client.query({ query });

  return <main>{data.now}</main>;
}

And this is how we can use the client inside a Client Component:

"use client";

import { getClient } from "@/lib/client";

import { gql } from "@apollo/client";

const query = gql`query Now {
    now(id: "1")
}`;

export default function Page() {
  const client = getClient();
  const { loading, data } = useQuery(query, { client });
  if (loading) { return <p>Loading</p>; }

  return <main>{data.now}</main>;
}

And that’s about it, we create a function that allows to get an instance of Apollo Client and we can use

We are also tinkering with the idea of creating a dedicated library that simplifies using Apollo Client with Next.js 13. Make sure to join our discord to be the first to know about updates on that!

Written by

Patrick Arminio

Stay in our orbit!

Become an Apollo insider and get first access to new features, best practices, and community events. Oh, and no junk mail. Ever.

Make this article better!

Was this post helpful? Have suggestions? Consider so we can improve it for future readers ✨.

Similar posts

February 27, 2023

Nullability in GraphQL and Apollo Kotlin

by Benoit Lubek

Company