A brief introduction to a Headless CMS App: Next.js, Apollo, and Contentful with GraphQL.

Daniel Correa
7 min readApr 7, 2020

--

Introduction

This article will cover a basic setup for a Next.js with Apollo and Contentful + GraphQL project. The setup suggested here is based on my own experience. I’m going to attach some useful links that helped me to define the approach I took.

Before getting into the topic, the following concepts should be clear:

  • Next.js: SSR React.js Framework.
  • Apollo: GraphQL implementation to provide a data layer.
  • GraphQL: A query language for modern web applications.
  • Contentful: A Headless CMS.

Source code of the example project

Article Collection

The goal of this example project is to have a web application that fetches an article’s information from Contentful using GraphQL and Apollo and then renders it using Next.

Data Layer

In order to build the required data architecture we need the following content types defined in our Contentful space:

  • Article
    |- name [unique, required] {short text}
    |- tag [unique, required] {short text, kebabcase}
    |- title [required] {short text}
    |- image {media — unique type of Image}
    |- author [required] {one reference — unique type of Author}
    |- publishDate [required] {date and time}
    |- body [required] {rich text}
  • Author
    |- name [unique, required] {short text}
    |- authorName [required] {short text}
    |- role [required] {short text}

Let’s create these Content Types in our Contentful space:

Article Content-Type
Author Content-Type

We can now create and retrieve some content. Let’s create it!.

Content create to

With the content set, we can get it using GraphQL Playground for Chrome.

Contentful provides a GraphQL API using the following URL:

https://graphql.contentful.com/content/v1/spaces/<space-id>/environments/<environment-id>

We need to set the Content Delivery API access token in the HTTP Header.

{
“Authorization”: “Bearer <access-token>”
}

** You can check all the GraphQL Content API documentation here.

For now on, we can execute simple queries like:

query Articles {
articleCollection {
items {
tag
title
image {
url
description
}
author {
authorName
role
}
publishDate
body {
json
}
}
}
}

Let’s see the response working on GraphQL Playground for Chrome:

Query executed preview

We got all the Article's data!. That’s cool, we have a Content Architecture working now. What we need next? a cool Client App that translates this data into powerful components.

Next App

Create the Next.js App:

npx create-next-app article-collectioncd article-collection

Install the different dependencies we need for the project :

yarn add apollo-cache-inmemory apollo-client apollo-link-http graphql graphql-tag

Run the following command and check your Next.js project working on the following URL:

yarn devhttp://localhost:3000

Having our Next project up and running we can proceed to our Apollo Client configuration.

Apollo Client

First up, let’s define our access token, space id and environment id to our .env.local file.

CONTENTFUL_ACCESS_TOKEN=<access-token>CONTENTFUL_ENVIRONMENT=<environment-id>CONTENTFUL_SPACE_ID=<space-id>

We can find this information in the Environment Panel. There, we can also check the environment we would like to grant access to with the API Key created.

API Key configuration panel

By having our local environment file set with the token and ids, let’s now create our apollo client instance:

  • Create a next.config.js file:
touch next.config.js

Add the tokens to the publicRuntimeConfig:

module.exports = {  publicRuntimeConfig: {    CONTENTFUL_ACCESS_TOKEN: process.env.CONTENTFUL_ACCESS_TOKEN,    CONTENTFUL_ENVIRONMENT: process.env.CONTENTFUL_ENVIRONMENT,    CONTENTFUL_SPACE_ID: process.env.CONTENTFUL_SPACE_ID,  },};
  • Create the client.js file inside a folder named apollo:
mkdir apollo touch client.js

Let’s now configure an Apollo Client instance. Check the documentation if you would like to understand how this instance works and its parameters.

A basic instance will require:

=> A HttpLink instance where we define credentials and API endpoints.
=> An InMemoryCache instance, as recommended by apollo.

Our Apollo Client instance will look like the following:

const SPACE = publicRuntimeConfig.CONTENTFUL_SPACE_ID;
const ENVIRONMENT = publicRuntimeConfig.CONTENTFUL_ENVIRONMENT;
const TOKEN = publicRuntimeConfig.CONTENTFUL_ACCESS_TOKEN;
const CONTENTFUL_URL = `https://graphql.contentful.com/content/v1/spaces/${SPACE}/environments/${ENVIRONMENT}`;
const httpLink = createHttpLink({
fetch,
uri: CONTENTFUL_URL,
headers: {
authorization: `Bearer ${TOKEN}`,
'Content-Language': 'en-us',
},
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache().restore(initialState || {}),
});

To make our Apollo Client accessible for all components, we need a provider.

Two approaches come to mind at implementing this provider:

=> Following Next.js example: We have to implement a custom HOC that works as a provider for the application.
=> Using Next with Apollo library: It has an implementation of the HOC that we are going to use as a provider, exposing some configurable options.

I went with the latter approach. It saves some time and we don’t want to reinvent the wheel. Let’s install it then:

yarn add next-with-apollo

Return the Apollo Client instance with the HOC provided by Next with Apollo:

export default withApollo(
({ initialState }) =>
new ApolloClient({
link: httpLink,
cache: new InMemoryCache().restore(initialState || {}),
}),
);
  • Use the provider in _app.js file:

With the provider and the client, we wrap our Next App in the _app.js file.

Here we can see the final version of the client.js and the _app.js files:

Having all set, we can now start querying!.

Query something!

We have two options when it comes to doing queries:

=> Using the useQuery hook from Apollo: We will have to do queries when the React Component is client-side. This option is not available for SSR since it is a React Hook.
=> Using our Apollo Client instance: The instance is available during SSR! , we can use it to query in the server.

Since our goal is to populate the page in SSR, we have to do queries using the Apollo Client instance. We do that inside the getInitialProps lifecycle method.

Let’s do a query to fetch a list of articles, and render it on our homepage.

Create a folder called “queries and create an articles.js file inside it:

mkdir queriescd queriestouch articles.js 

Add the query in the articles.js file. For that, we use graphql-tag:

import gql from 'graphql-tag';export const GET_ALL_ARTICLES = gql`
query Articles {
articleCollection {
items {
tag
title
image {
url
description
}
author {
authorName
role
}
publishDate
}
}
}
`;

Then, import and use the query in pages/index.js file by using getInitialProps:

Home.getInitialProps = async ({ apolloClient }) => {
const { data, loading, error } = await apolloClient.query({
query: GET_ALL_ARTICLES,
});
return {
data,
loading,
error,
};
};

Add some fancy styles and see the result!

Home page showing the list of Articles we have

Bonus

Add a page that fetches and renders a single Article instance.

Use the Next dynamic routing approach to have dynamic URLs for Article instances. Create a folder called article inside your pages directory. Then create a file called [tag].js.

cd pagesmkdir article cd article touch [tag].js

Add the component Article that will render out the Article detail page. You are free to build the component as you like.

Add the GET_ARTICLE query inside queries/articles.js. This query will have a tag parameter that identifies each article; remember, it is a unique field.
It also has a limit: 1 parameter.

export const GET_ARTICLE = gql`
query Article($tag: String!) {
articleCollection(where: { tag: $tag }, limit: 1) {
items {
tag
title
image {
url
description
}
author {
authorName
role
}
publishDate
body {
json
}
}
}
}
`;

We are now able to use the query inside [tag].js file.

The tag value is available in the ctx object, next to our Apollo Client instance. Get it from the query object as follows:

const {
apolloClient,
query: { tag },
} = ctx;

At this point, the getInitialProps method should look like this:

Article.getInitialProps = async ctx => {
const {
apolloClient,
query: { tag },
} = ctx;
const { data, loading, error } = await apolloClient.query({
query: GET_ARTICLE,
variables: {
tag,
},
});
return {
data,
loading,
error,
};
};

Add some styles and see the result!.

Article Detail page

If you are wondering how to parse the RTE data into a React component, you can find the answer here: Rich Text React Render.

Some important links:

Source code of the example

Rich Text React Render

About me:

Daniel Correa

Please let me know in the comments what you would like to add to this guide.

--

--