Written by Coner Murphy✏️
Apollo at its heart is a GraphQL implementation that helps people manage their data. They also make and maintain a GraphQL client (Apollo Client) which 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 why we should use the Apollo Client with Next.js and how we can use the Apollo Client with Next.js to render pages via the three rendering methods Next.js supports; static site generation (SSG), server-side rendering (SSR), and client-side rendering (CSR).
So, without further ado, let’s get started.
Why 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:
- Out-of-the-box support for caching
- Built-in loading and error states
- 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 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 on their documentation; if you’re interested in reading it, you can here.
Built-in loading and error states
The Apollo Client has a custom React Hook built into it called block [useQuery]
, which we will use in our example application, and gives us built-in 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 built-in 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.
What this means for us is 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 another great example of how the Apollo Client helps speed up development and make us more efficient developers when working with GraphQL.
Next.js rendering methods
Next.js has three rendering methods that we can use in conjunction with the Apollo Client. These are SSG, SSR, and CSR. 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.
Because of this, 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, this method of rendering pages is great. While SSG is great for landing pages or blog pages, an inherent downside of using SSG is that you can only update the data at build time. This means two things:
- The data must be available at build time for the server to query it
- 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.
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 on the page.
This rendering method keeps the 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 like 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 the situation you are in.
Now, let’s build an example application to show off these three methods in action.
How to use Apollo with Next.js
Before we look at making our application using 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 the data on this API seems to be outdated and has not been updated recently, but it 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>
Note that 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 three pages in our application that will showcase the three 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 but will use the built-in getStaticProps()
function from Next.js instead. 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 then return a props
object from the getStaticProps()
function as required by Next.js 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:
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 to not render the page statically at build time, but instead on each request to the server.
Now, if we navigate to our ssr
page on our application, we should see a page that looks identical to the homepage that is using SSG but is now rendered by the server per each request.
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 allow us to use this, let’s first wrap our application in the ApolloProvider
.
To 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
, create a new file within that directory called ClientOnly.js
, then paste in the below code:
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 10 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 I mentioned earlier. To use this Hook, we define a GraphQL query and then pass it to the Hook, which then returns three values we can use in our application:
-
data
, which is the data returned from the query -
loading
, a boolean value that controls rendering while data is fetched -
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 in 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, and that’s 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 the 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.
Our application is now complete, we have implemented the Apollo Client with all three 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, 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 hard to reproduce. If you’re interested in monitoring and tracking Redux 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 — start monitoring for free.
Top comments (0)