Coner Murphy Web developer, content creator, and tech entrepreneur building phytype.com. I post about web dev, tech entrepreneurship, and financial freedom on my Twitter and blog.

Why use Next.js with Apollo

13 min read 3733

Why Use Next.js With Apollo

At its heart, Apollo is a GraphQL implementation that helps people manage their data. They also make and maintain a GraphQL client (Apollo Client) that we can use with React frameworks like Next.js.

The Apollo Client is a state management client that allows you to manage both local and remote data with GraphQL and you can use it to fetch, cache, and modify application data.

In this article, we’ll discuss:

So, without further ado, let’s get started.

Why we should use Apollo Client with Next.js

First, let’s look at why we should use the Apollo Client with Next.js. There are three key reasons to use the Apollo Client:

  1. Out-of-the-box support for caching
  2. Inbuilt loading and error states
  3. Declarative approach to data fetching

Out-of-the-box support for caching<

In Apollo’s own words, “Caching a graph is no easy task, but we’ve spent two years focused on solving it.”

Apollo has invested serious amounts of time in making their caching process the most efficient and effective they can. So, why try and reinvent the wheel? If you’re fetching data in your application and working with GraphQL, then the Apollo Client is a great way of handling your data.

Another benefit is that there is minimal setup required for developers using the client. Apollo even labels it as “zero-config.” We will see this in our application example further along in the post, but to set up caching in an application, all we need to do is add the code below to our application:

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

const client = new ApolloClient({
  cache: new InMemoryCache()
})

Apollo has a great write-up about their zero-config caching in their documentation; if you’re interested in reading it, you can here.

Inbuilt loading and error states

The Apollo Client has a custom React Hook built into it called useQuery, which we will use in our example application, and gives us inbuilt loading and error states for us to use.



While this doesn’t sound impressive, what it means for developers is that we don’t need to spend time implementing this logic ourselves, we can just take the booleans the Hook returns and change our application rendering as required.

Thanks to these inbuilt states, using the Apollo Client means we can spend less time worrying about implementing data fetching logic and focus on building our application.

Declarative approach to data fetching

Another benefit of the way the Apollo Client implements the useQuery Hook is that data fetching with the Apollo Client is declarative rather than imperative.

This means that we only need to tell Apollo what data we want and it gets it for us; we don’t need to write the logic and give it a list of step-by-step instructions on how to do it or handle it. Once again, this is a great example of how the Apollo Client helps speed up development and make us more efficient developers when working with GraphQL.

Using Apollo with each of Next.js’s rendering methods

Next.js has four rendering methods that we can use in conjunction with the Apollo Client. These are static site generation, server-side rendering, incremental static regeneration, and client-side rendering.

Let’s take a look at each of these, what they do and how they work in Next.js. Then, we will build our application to showcase how the Apollo Client works with Next.js and these rendering methods.

Static site generation (SSG)

With SSG, our pages are generated and converted to HTML on the server at build time ahead of the user requesting them. This means that when the user requests the page, the only thing that must be done is to send the generated HTML to the user and display it.

As a result, the user experiences a faster load time because there is no rendering on the server when the request comes in.

For a lot of websites, like landing pages or blogs, this method of rendering pages is great. Still, there is an inherent downside of using SSG, which is that you can only update the data at build time. This means two things:


More great articles from LogRocket:


  1. The data must be available at build time for the server to query it
  2. If the data updates after the page is built, then the data is outdated and can only update via deploying a new build

One way of solving this stale data issue is by using SSR, so let’s take a look at that.

Incremental static regeneration (ISR)>

SSG is an amazing way to render your static content, but if that content changes often, SSG isn’t really practical. This is because it requires an entire site rebuild every time you want to source or display new data.

However, this is the exact issue ISR was designed to solve.

With ISR, you can use static generation on a per-page basis, but unlike with SSG, ISR doesn’t require you to rebuild the entire site every time you want to refresh the data. In practice, this means you can keep the benefits of static rendering while scaling to thousands, if not millions, of pages.

This is achieved through caching. When a page using ISR is requested, the initial request and any requests received during the defined revalidation period are shown on the cached page. Any requests submitted after the revalidation period has elapsed will still show the (stale) cached page but, in the background, Next.js will trigger a rebuild of the page. Once the new page is ready, Next.js will invalidate the old cache and show the updated page.

In short, if you want the benefits of static rendering/generation without needing to rebuild your entire website each time, ISR is the rendering method for you.

Server-side rendering (SSR)

With SSR, the server still generates pages like SSG, but instead of generating once at build time, it generates for each request the user sends. This solves the issues that plague SSG because we can update content to the latest version between deployments of the website.

But, a downside of this rendering method is that by rendering on each request to the server, we add extra processing time that can increase the response latency of the server.

Client-side rendering (CSR)

If you want to keep the server response latency down, using CSR is helpful. CSR is how a typical React application works; React renders a page and then requests the data. Once it retrieves the data, it will display it on the page.

This rendering method keeps pages loading fast but it does have the downside of not being compatible with SEO crawlers that crawl the generated HTML. This is because the data doesn’t generate into HTML on the server as with SSR and SSG; instead, it is added to the page once it is rendered by the client.

As you can see, the three different rendering strategies that Next.js supports all have their pros and cons. The rendering method you choose for the page and how you fetch data inside of Next.js with the Apollo Client will depend on your situation.

Now, let’s build an example application to show off these four methods in action.

How to use Apollo with Next.js

Before we look at making our application with Next.js and Apollo, let’s take a brief moment to look at the API we’ll consume in the application.

This API is an unofficial SpaceX API that tracks all their past and upcoming missions. You can play around with the data in the API here if you’re interested. However, note that while the data on this API seems to be outdated and has not been updated recently, it still works for this tutorial.

With the API covered, let’s get to building our application. First, let’s create a new Next.js application; we can do this by running the following command in our terminal:

npx create-next-app <name-of-your-project>

I named my project nextjs-apollo, but you can name it whatever you wish.

With this sorted, we can move into our project folder by running the following command in our terminal:

cd nextjs-apollo

Now, the first thing we want to do is install the Apollo Client, which we can do with this command:

npm install @apollo/client graphql

We have now finished installing all of the necessary dependencies for our project. But, before we can start building, we need to configure our Apollo Client to work with our API.

To do this, open up your project in your IDE of choice and create a new file in the root of the project called apollo-client.js.

Inside this file, put the following code; this will create the Apollo Client and allow us to query the API throughout our Next.js application:

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

const client = new ApolloClient({
    uri: "https://api.spacex.land/graphql",
    cache: new InMemoryCache(),
});

export default client;

With this setup, we can now look at building out four pages in our application that will showcase the four rendering methods we covered earlier.

Fetching data with Apollo Client for SSG pages

To query data for SSG pages, we won’t use any Hooks for data fetching from the Apollo Client; instead, we will use the inbuilt getStaticProps() function from Next.js. By using getStaticProps() and the Apollo Client together, we can create SSG pages easily.

First, navigate to and open pages/index.js. Inside this file, add the following code to the bottom of the page:

export async function getStaticProps() {
  const { data } = await client.query({
    query: gql`
      query NextLaunch {
        launchNext {
          mission_name
          launch_date_local
          launch_site {
            site_name_long
          }
        }
      }
    `,
  });
  return {
    props: {
      nextLaunch: data.launchNext,
    },
 };
}

Then, add these lines to the top of the file:

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

With these code sections added, when we start the development server and Next.js generates the pages for our application, it will await the fetching of the requested data from our API. Once the data returns, it’s given to the page to be built.

We will layout the page in a moment, but first, let’s cover what is happening in the code above.

First, we define a query that contains the fields we want to return from the API. After this, we use the client we imported to query this data. Once the data is returned from the API, we destructure out the data object.

Although you can’t see data anywhere in the requested fields from the API, GraphQL will automatically wrap the returned data in a data object. So, by destructuring it here, we save ourselves having to do .data later on.

Finally, we return a props object from the getStaticProps() function, which Next.js requires for the page generation to work. Inside of this props object, we pass in the data we want to use on the page.

Let’s now lay out our homepage and display the data we just fetched on the page. Replace your Home function in your index.js file with the following code:

export default function Home({nextLaunch}) {

  const {mission_name, launch_date_local, launch_site} = nextLaunch;
  const nextLaunchDate = fetchDate(launch_date_local).join('/');

  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <h1 className={styles.title}>
          Next SpaceX Launch
        </h1>
        <p className={styles.description}>
          <span>🚀 Mission name: <strong>{mission_name}</strong></span>
          <span>📅 Date: <strong>{nextLaunchDate}</strong></span>
          <span>🏠 Launched from: <strong>{launch_site.site_name_long}</strong></span>
        </p>
      </main>
    </div>
  )
}

Then, for our dates to display properly, add in the following function above the Home function we just replaced:

function fetchDate(date) {
  const newDate = new Date(date);

  const day = newDate.getDate();
  const month = newDate.getMonth();
  const year = newDate.getFullYear();

  return [day, month, year];
};

Finally, replace the contents of your Home.module.css file with the CSS code below:

.container {
  padding: 0 2rem;
}
.main {
  min-height: 100vh;
  padding: 4rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.footer {
  display: flex;
  flex: 1;
  padding: 2rem 0;
  border-top: 1px solid #eaeaea;
  justify-content: center;
  align-items: center;
}
.footer a {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-grow: 1;
}
.title a {
  color: #0070f3;
  text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
  text-decoration: underline;
}
.title {
  margin: 0;
  line-height: 1.15;
  font-size: 4rem;
}
.title,
.description {
  text-align: center;
}
.description {
  display: flex;
  flex-direction: column;
  font-size: 1.5rem;
}
.code {
  background: #fafafa;
  border-radius: 5px;
  padding: 0.75rem;
  font-size: 1.1rem;
  font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
    Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 800px;
}
.card {
  margin: 1rem;
  padding: 1.5rem;
  text-align: left;
  color: inherit;
  text-decoration: none;
  border: 1px solid #eaeaea;
  border-radius: 10px;
  transition: color 0.15s ease, border-color 0.15s ease;
  max-width: 300px;
}
.card:hover,
.card:focus,
.card:active {
  color: #0070f3;
  border-color: #0070f3;
}
.card h2 {
  margin: 0 0 1rem 0;
  font-size: 1.5rem;
}
.card p {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.5;
}
.logo {
  height: 1em;
  margin-left: 0.5rem;
}
.missionContainer {
  margin: 5rem;
}
@media (max-width: 600px) {
  .grid {
    width: 100%;
    flex-direction: column;
  }
}

Now, we can start our development server with npm run dev in our terminal and navigate to the homepage; by default, it should be at http://localhost:3000/. Once at this page, you should be greeted with something that looks like this:

>>>>> gd2md-html alert: inline image link here (to images/image2.png). Store image on your image server and adjust path/filename/extension if necessary.
(Back to top)(Next alert)
>>>>>

alt_text

Fetching data with Apollo Client for ISR pages

Because the API in our example app isn’t providing up-to-date data or changing its data often, we won’t see the benefits of ISR in action — but that shouldn’t stop us from setting up a page to use it.

The first thing we want to do is duplicate our index.js page and rename it to isr.js. Following this, we want to rename the function export on the page to the below.

export default function ISR({nextLaunch}) {
….rest of code here

Then all we need to do to set up ISR on this page instead of SSG is amend our getStaticProps function at the bottom of the page. At the bottom of this function, where we return the props from data fetching, we also need to return a revalidate prop.

 return {
    props: {
    nextLaunch: data.launchNext,
    },
    revalidate: 10, // In seconds <- Add this line
 };

With this line added to our getStaticProps function, ISR is now set up and configured on our /isr route with a revalidation period of 10 seconds.

Now, as mentioned earlier, because the data is out of date, it will show the same output as the SSG page from before. If we had more current or rapidly changing data, ISR would refresh it when requests are made outside of the revalidation period of ten seconds.

Fetching data with Apollo Client for SSR pages

Generating SSR pages with data using the Apollo Client is easy now that we set up our SSG page.

First, let’s duplicate our index.js file and rename it to ssr.js. Then, all we need to do is replace the getStaticProps() function we used for the SSG pages with getServerSideProps().

By changing the function to getServerSideProps(), we instruct Next.js not to render the page statically at build time and instead do so on each request to the server.

Now, if we navigate to our ssr page in our application, we should see a page that looks identical to the homepage that uses SSG but is now rendered by the server per request.

The output of our SSG data fetching experiment with Next.js and Apollo Client

Fetching data with Apollo Client for CSR pages

CSR rendering is a bit more involved than what we had to do for our SSG and SSR pages. Since we must now fetch data inside React, we want to use the useQuery Hook provided by the Apollo Client. To use this, let’s first wrap our application in the ApolloProvider.

Open up the _app.js file, found inside the pages directory alongside the other two files, index.js and ssg.js. Once, inside the _app.js file, let’s import the ApolloProvider and the client we created at the start of this tutorial:

import { ApolloProvider } from "@apollo/client";
import client from "../apollo-client";

Then, let’s replace the existing MyApp component with the one below:

function MyApp({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
}

With the ApolloProvider now wrapping our application, we can use the useQuery Hook from the Apollo Client anywhere in our application. But, before we can start using this Hook and fetching data for our CSR page, we must address one more thing.

If we implement the useQuery Hook to fetch data on our CSR pages, then we can make requests to our API while the page renders. We want to avoid doing this, though, because it means the page will generate before the API requests can return and Next.js passes the data to the page.

To resolve this, we must implement a custom component called ClientOnly. This component ensures that we only request data from the browser and not while the page renders on the server before the user’s request.

To create this new component, create a new directory at the root level called components and create a new file within that directory called ClientOnly.js. Then, paste in the below code, which was originally written by Josh Comeau:

import { useEffect, useState } from "react";

export default function ClientOnly({ children, ...delegated }) {
  const [hasMounted, setHasMounted] = useState(false);

  useEffect(() => {
    setHasMounted(true);
  }, []);

  if (!hasMounted) {
    return null;
  }

  return <div {...delegated}>{children}</div>;
}

With this component sorted, we can go on and implement data fetching for our CSR page. To do this, create another new component called PastLaunches.js; this component fetches the last ten launches and displays them to the user.

Inside the pastLaunches.js file, paste in the following code:

import { useQuery, gql } from "@apollo/client";
import styles from "../styles/Home.module.css";

const QUERY = gql`
  query PastLaunches {
    launchesPast(limit: 10) {
        mission_name
        launch_date_local
        launch_site {
        site_name_long
        }
    }
}
`;

export default function PastLaunches() {
    const {data, loading, error} = useQuery(QUERY);

    if (loading) {
        return (
            <h2>Loading Data...</h2>
        );
    };

    if (error) {
        console.error(error);
        return (
            <h2>Sorry, there's been an error...</h2>
        );
    };

    const {launchesPast} = data;

    return (
        <>
            {launchesPast.map((launch) => (
                <div key={launch.mission_name} className={styles.missionContainer}>
                    <p className={styles.description}>
                        <span>🚀 Mission name: <strong>{launch.mission_name}</strong></span>
                        <span>📅 Date: <strong>{launch.nextLaunchDate}</strong></span>
                        <span>🏠 Launched from: <strong>{launch.launch_site.site_name_long}</strong></span>
                    </p>
                </div>
            ))}
        </>
    )
}

At the top of the PastLaunches() function, you can see the useQuery() Hook from the Apollo Client that I mentioned earlier. To use this Hook, define a GraphQL query and then pass it to the Hook, which then returns three values we can use in our application:

  1. data, which is the data returned from the query
  2. loading, a boolean value that controls rendering while data is fetched
  3. error a boolean value that controls rendering if there is an error fetching the data

With this component created, all we need to do is add the new page file for our CSR page and consume these two components. For this, create a new file in our pages directory called csr.js, and paste in the following code:

import Head from "next/head";
import styles from "../styles/Home.module.css";
import ClientOnly from "../components/ClientOnly";
import PastLaunches from "../components/PastLaunches";

export default function ClientSide() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Past SpaceX Launches</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <h1>Past SpaceX Launches</h1>
        <ClientOnly>
          <PastLaunches />
        </ClientOnly>
      </main>
    </div>
  );
}

This code is a lot smaller than the code used for the SSG and SSR pages because we moved all of the displaying and data fetching code into the PastLaunches component.

But, most importantly, you can see that we wrap the PastLaunches component in our ClientOnly component from earlier to prevent data fetching from anywhere but the browser.

If we now navigate to http://localhost:3000/csr you can see our data fetched on the client. Initially, it displays a loading state while fetching the data, and then once the data is retrieved, it switches and displays it to the user.

The output of our CSR experiment with Next.js and Apollo Client

Our application is now complete, we have implemented the Apollo Client with all four of the different rendering technologies available in Next.js.

Conclusion

Throughout this post, we looked at what the Apollo Client is, why we should use it, and how we can implement it in three different ways in Next.js through SSG, ISR, SSR, and CSR.

If you want to see the GitHub repository for this project, you can see it here.

I hope you found this article on why and how we can use Apollo in Next.js helpful. If you did, please consider following me over on Twitter, where I post helpful and actionable tips and content on the JavaScript ecosystem and web development as a whole.

Or, if Twitter isn’t your thing, visit my blog for more of my content.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Coner Murphy Web developer, content creator, and tech entrepreneur building phytype.com. I post about web dev, tech entrepreneurship, and financial freedom on my Twitter and blog.

2 Replies to “Why use Next.js with Apollo”

  1. Thanks for this great tutorial (and the many other great LogRocket tutorials – I keep finding myself coming here when googling for solutions so your SEO + content strategy are working great too 🙂 )

    I am using Nextjs (SSG) + Strapi + Apollo/client for a website and everything is working perfectly. However I am trying to reduce client JS bundle size on Nextjs and apollo/client is the big biggest chunk that I am struggling to reduce/remove/split.

    All my pages are SSG and therefore since apollo/client is only used within getStaticProps, I believed that apollo/client should not be bundled for the client side.

    By a long process of elimination, I can see that even when I remove all references to apollo/client within /pages folder (by commenting out all the getStaticProps functions, it is still bundled and just simply having the following code within `lib/server-api.js` ensures it is bundled for the client-side:

    “`
    import {
    ApolloClient,
    InMemoryCache,
    gql,
    HttpLink,
    concat,
    ApolloLink,
    } from ‘@apollo/client’;

    const httpLink = new HttpLink({
    uri: `${
    process.env.NODE_ENV === ‘development’
    ? ‘http://localhost:1337’
    : process.env.NEXT_PUBLIC_STRAPI_API_URL
    }/graphql`,
    });

    const authMiddleware = new ApolloLink((operation, forward) => {
    operation.setContext(({ headers = {} }) => ({
    headers: {
    …headers,
    authorization: `Bearer ${
    process.env.NODE_ENV === ‘development’
    ? process.env.API_TOKEN_LOCAL
    : process.env.API_TOKEN
    }`,
    },
    }));

    return forward(operation);
    });
    const client = new ApolloClient({
    cache: new InMemoryCache(),
    link: concat(authMiddleware, httpLink),
    });
    “`
    When I comment out the above, it is no longer bundled. But as you can see from above, this code is located in `lib/server-api.js` and nothing is exported.

    So why is it bundling to the client-side?

    Any help greatly appreciated!!

Leave a Reply