Apollo Client is a popular GraphQL client that can cache the results of your queries, among many other features. If you have an entirely client-side rendered (CSR) front-end app and want to do all your data fetching in the browser, you can simply start off with an empty Apollo Client cache and populate it with data as GraphQL requests execute.
But what if you want to use server-side rendering (SSR), static site generation (SSG), or incremental static regeneration (ISR), though? How can you fetch data ahead of time on the server? Can the Apollo Client’s cache initialize with that data on the client so that your queries don’t need to be rerun?
Thankfully, there is a way to fetch data on the server, populate Apollo Client’s cache, send that cache data to the client, and use it to “rehydrate” or initialize Apollo Client’s cache in the browser. Cache rehydration gives the benefits of SSR/SSG/ISR (a fully rendered HTML document on the initial page load, great SEO, etc.). Best of all, you still have the data in Apollo Client’s cache. This prevents your app from making unnecessary network requests for data you already have.
In this blog post, we’ll cover how to set up Apollo Client cache rehydration in a Next.js app.
Project Setup
Before setting up cache rehydration, fulfill the following prerequisites:
- Clone down the repo to your machine: https://github.com/kellenmace/apollo-client-cache-rehydration-in-next-js
- Run
npm install
(oryarn
) to install the project’s dependencies. - Create a
.env.local
file in the root directory with this variable defined:NEXT_PUBLIC_WORDPRESS_API_URL=http://my-local-site.local/graphql
. Swap outhttp://my-local-site.local
with the domain of a WordPress site that has WPGraphQL installed. - Run
npm run dev
to get the app up and running locally.
Approach
Server-side Rendering (SSR) through the Apollo client follows this approach:
- Use Apollo Client’s
getDataFromTree()
function to get the results for all queries throughout your entire component tree for the current page. - Extract Apollo Client’s cache and send that data to the client as a global
window.APOLLO_STATE
object. - When Apollo Client initializes on the client, call its
new InMemoryCache().restore()
function, passing in thewindow.APOLLO_STATE
object to seed it with the cache data from the server.
Although using the SSR approach works well with Next.js, the recommended way to do Apollo Client cache rehydration differs from that. We will instead do this:
- Use Next’s
getStaticProps()
orgetServerSideProps()
function to do our data fetching and populate Apollo Client’s cache. - Extract Apollo Client’s cache and add it as a
pageProps
prop for the current page. - When Apollo Client initializes on the client, call its
new InMemoryCache().restore()
function, passing in the cache data from the server.
For more information on how the Apollo client works, check out this official Next.js example project. We’ll use that same approach for our purposes, except the code examples will be written in TypeScript rather than JS, and all the data fetching examples will be specific to WordPress and WPGraphQL.
Install & Configure Apollo Client
Per Apollo Client’s getting started documentation, the @apollo/client
and graphql
NPM are already installed. The deepmerge
and lodash
packages have also been installed, which our project leverages.
To start configuring the client, open the lib/apolloClient.ts
file in a code editor. It looks like this:
import { useMemo } from 'react';
import { ApolloClient, HttpLink, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { relayStylePagination } from "@apollo/client/utilities";
import merge from 'deepmerge';
import isEqual from 'lodash/isEqual';
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: new HttpLink({
uri: process.env.NEXT_PUBLIC_WORDPRESS_API_URL, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
}),
cache: new InMemoryCache({
// typePolicies is not required to use Apollo with Next.js - only for doing pagination.
typePolicies: {
Query: {
fields: {
posts: relayStylePagination(),
},
},
},
}),
})
}
export function initializeApollo(initialState: NormalizedCacheObject | null = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
});
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function addApolloState(client: ApolloClient<NormalizedCacheObject>, pageProps: any) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
}
return pageProps;
}
export function useApollo(pageProps: any) {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const store = useMemo(() => initializeApollo(state), [state]);
return store;
}
Code language: JavaScript (javascript)
Let’s walk through each of those function definitions.
createApolloClient()
This function is responsible for creating a new instance of Apollo Client.
We set Apollo Client’s ssrMode
option to true
if the code is running on the server, and to false
if it’s running on the client.
The uri: process.env.NEXT_PUBLIC_WORDPRESS_API_URL
line tells Apollo Client to use the GraphQL endpoint defined in your .env.local
file for all network requests.
initializeApollo()
As its name suggests, this function initializes Apollo Client. It merges the initial state (data passed in from getStaticProps()
/ getServerSideProps()
) with the existing client-side Apollo cache, then sets that new, merged data set as the new cache for Apollo Client.
addApolloState()
This function takes the pageProps
returned from getStaticProps()
/ getServerSideProps()
for the current page and adds to them Apollo’s cache data. From there, Next.js takes care of passing Apollo’s cache data, along with any other page-specific props into the page component.
useApollo()
This function calls initializeApollo()
to get an instance of Apollo Client that has Apollo’s cache data added to it. This client is ultimately passed in as a prop to the ApolloProvider
that Apollo Client provides.
Render an ApolloProvider
Inside of _app.tsx
, we call useApollo()
to get a new instance of Apollo Client, passing to it the pageProps
for the current page. The apolloClient
instance is then passed in as a prop to the ApolloProvider
component, which wraps our application.
import { AppContext, AppInitialProps } from "next/app";
import { ApolloProvider } from "@apollo/client";
import { useApollo } from "../lib/apolloClient";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppContext & AppInitialProps) {
const apolloClient = useApollo(pageProps)
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default MyApp;
Code language: JavaScript (javascript)
How to Use in Page Components
Let’s see how we can use this Apollo Client cache rehydration superpower in our Next.js page components.
Open up pages/ssg.tsx
. A simplified version of that file looks like this:
import { gql, useQuery } from "@apollo/client";
import { initializeApollo, addApolloState } from "../lib/apolloClient";
const POSTS_PER_PAGE = 10;
const GET_POSTS = gql`
query getPosts($first: Int!, $after: String) {
posts(first: $first, after: $after) {
# etc.
}
}
`;
export default function SSG() {
const { loading, error, data } = useQuery(GET_POSTS, {
variables: {
first: POSTS_PER_PAGE,
after: null,
}
});
// etc.
}
export async function getStaticProps(context: GetStaticPropsContext) {
const apolloClient = initializeApollo();
await apolloClient.query({
query: GET_POSTS,
variables: {
first: POSTS_PER_PAGE,
after: null,
}
});
return addApolloState(apolloClient, {
props: {},
});
}
Code language: JavaScript (javascript)
Inside of Next.js’ getStaticProps()
function, we call initializeApollo()
to get an instance of Apollo Client that we can use for making GraphQL requests.
We then call the apolloClient.query()
method, passing in the exact same query and variables that are used inside of our SSG
page component when it calls the useQuery()
hook. You’ll notice that we’re not even using the return value that apolloClient.query()
provides. This is because we don’t need it – we’re only calling apolloClient.query()
to populate Apollo Client’s cache with the data for the queries executed.
When our SSG
page component is rendered in the browser and the useQuery()
hook is called, Apollo Client will “see” that the data for the query being executed already exists it its cache, and will use that data instead of making a network request. This will result in the content on the page rendering immediately. Visit http://localhost:3000/ssg in your browser, open up the Network tab in the DevTools and refresh the page to confirm that no client-side GraphQL requests execute.
More info on Next.js’ getStaticProps()
function can be found here: https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation.
SSR Page
Open up pages/ssr.tsx
and visit http://localhost:3000/ssr. You can see that this file functions exactly the same as ssg.tsx
, except that Next.js passes in the page props on every request since we’re using getServerSideProps()
instead of getStaticProps()
. Again, you can open up the Network tab in the DevTools and refresh the page to confirm that no client-side GraphQL requests execute.
More info on Next.js’ getServerSideProps()
function can be found here: https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering.
CSR Page
To prove that we still have the option to do data fetching on the client, if desired, open up pages/csr.tsx
and visit http://localhost:3000/csr. You can see that this page does not fetch any data on the server, since we’re not using getStaticProps()
or getServerSideProps()
. If you open up the Network tab in the DevTools and refresh the page, you can confirm that the GraphQL network requests execute client-side when the page first loads. You can do this if you want your request to run in the browser only, and not on the server.
Mixed SSG + CSR Page
Finally, open up pages/blog.tsx
. Make sure you have eleven or more posts created in your WordPress backend, then visit http://localhost:3000/blog.You can see that inside of getStaticProps()
, an Apollo Client query is executed to get the initial list of ten blog posts, which immediately get rendered to the page when it loads. If more posts exist in the WordPress backend, the user is presented with a Load more
button. Clicking this button results in Apollo Client’s fetchMore()
function being called, which fires off a network request to get the next five posts and appends them to the list. Once all posts have been loaded, the user sees an ✅ All posts loaded
message.
The blog.tsx
page shows how you can fetch some data on the server to populate Apollo’s cache. Also, the markup appears instantly when the page loads on the front-end. After, the page fires subsequent client-side requests to fetch additional data, adding it to Apollo’s cache..
How to Use this Code in Your Projects
In order to use Apollo Client for SSG/SSR/ISR in a Next.js project, you’ll need to follow these steps:
- Copy
lib/apolloClient.ts
into your project.
As stated above, set theprocess.env.NEXT_PUBLIC_WORDPRESS_API_URL
variable to reference the GraphQL endpoint of your headless WordPress backend.
The object containingtypePolicies
that is being passed intonew InMemoryCache()
in this file can be omitted if you’re not doing any pagination in your project (such as thefetchMore()
pagination demonstrated on thepages/blog.tsx
page). - In
pages/_app.tsx
, calluseApollo(pageProps)
to get an instance of Apollo Client, then pass that in as a prop to the<ApolloProvider />
component, as shown above. - Mimic the page components in the /pages directory of this project, depending on whether you want your pages to do their data fetching at build time (see
ssg.tsx
), at request time on the server (seessr.tsx
), on the client only (seecsr.tsx
), or a mixture of those (seeblog.tsx
).
Wrapping Up
I hope this walk-through helps you with leveraging Apollo Client for SSG/SSR/ISR data fetching on your next Next.js project.
And that’s it! Thanks so much for giving this post a read, and please reach out to us with any questions you have! ????