How to build a powerful blog with NextJS and Contentful
Tags: javascript, react, webdev
Published: 2019-09-24 11:05:52


Building a blog that scales can be quite easy if you know where to start.
In this article, we show you how you can build, in a few steps a robust blog, powered by our CMS of choice: Contentful.

However, if you prefer another CMS, with a couple of short steps, you could integrate with your favourite headless CMS.

We also explore how to add Google Analytics and generate sitemaps automatically.

So grab a cup of coffee, and let's get started!

To build our new blog, we chose a few tools that may or may not be familiar to you:
- __Next.JS__ (V9+) as our main framework,
- __React Markdown__ to parse and display the blog post's content,
- __Contentful__ as our Headless CMS,
- __Netlify__ for cloud hosting and deployment

## User Experience
To build a blog that appeals to your users, we need to define the user experience.

We define our requirements first. The blog has the following primary purposes:

- A user can see all the blog posts,
- A user can navigate a large number of blog posts using pagination,
- A user can filter by a topic that he is interested in,
- A user should be able to read a blog post

Navigation on your blog should be user-friendly. First-time readers can scan the topics and tags at a glance. Once they find a blog post that they are willing to read, they can directly navigate to it.

Users should also have the ability to filter by relevant topics.

![blog-sketch](//images.ctfassets.net/yy0ya4rtm8k6/j4OAoqbxNTa4x01RpBz50/e7455d4d9ecf658c18b27d866ba7d7f1/blog-sketch.jpeg)

You can add other requirements, but this should get you started in thinking about the design.
  
## Prerequisite
Make sure you have the latest stable node version and npm or yarn installed.

For this article, we use __npm__ to install the packages. However, feel free to use yarn or any other packaging manager you are comfortable with.

## Laying the foundation

### Setting up Next.Js

Getting started is easy. Enter one of the following commands to create your application:

```npx create-next-app```

or 

```
npm install -g create-next-app
create-next-app my-next-blog && cd my-next-blog
```

Follow the instructions in your terminal, and you should be good to go.

### Integrating Typescript

The latest version of Next.Js has Typescript directly baked in. 

To enable it, we first install the relevant packages. Navigate to your project, and run:

```npm install --save-dev typescript @types/react @types/node```

If you haven't already, rename the files within pages from `index.js` to `index.tsx` and then run the application:

```npm run dev```

Next.Js automatically creates a __`tsconfig.json`__ and sets up your project into Typescript. If you have a components folder, you can delete the folder for now.

We also want to include the CSS plugin for styling purposes.

```npm install --save @zeit/next-css```

Create a __`next.config.js`__ at the root of your project, including the following:

{% gist https://gist.github.com/abedzantout/ffb0c74f9fdd1ac9f361d471fd719920.js %}

## Application Architecture

Building a robust blog application requires us to think more about our application's architecture.

In the previous sections, we defined the user experience and formed a rough idea of what the blog will be. 

We should now think about implementation and architecture.

Next.Js already has a particular default setup when it comes to structuring your pages. All pages to be rendered exist under the `pages` folder.

We also consider SEO, data integration, and routing.

Let's define our folder structure in the following manner:

```
- core // contains everything related to fetching the data from our CMS, Google Analytics logic, and any data sharing service/logic, and is shared across the entire application
- shared // contains common components, helper functions
- public // static folder where we can store assets to be directly compiled by Next, this is mainly used to inject the robot.txt file
- assets // contain website specific assets such as images
- interfaces// contain different models
- pages // contains the pages
```

## Setting up Contentful
[Contentful](https://www.contentful.com/) is a powerful headless CMS that is easy to use and integrate.

While you can set up the content models needed using their dashboard, we will opt to build a script that does this process for us.

### Install the needed packages

Make sure you install the __contentful-cli__ and __contentful-migration__ before moving to the next step:

```npm install contentful-cli contentful-migration --save-dev```

### Building your Content Models
Content models allow us to better structure our entries (whether it is a blog, author or tag) by allowing us to specify the structure of the entries. Think of it as an interface for your different entries.

Create a folder called `utils` in your project folder and paste the following:

{% gist https://gist.github.com/abedzantout/130fab45a2ce4d051c7964f7d47c6209.js %}

To run the script, add the following to your scripts in __package.json__:

```
"scripts": {
...
"populate:contentful": "contentful space migration --space-id <YOUR_SPACE_KEY> utils/contentful.js --yes"
}
```

Navigate to your Contentful dashboard to find your space key and token.

Replace __YOUR_SPACE_KEY__ with your actual space key, and then run the command:

```npm run populate:contentful```

The above command should populate your CMS will all the content models we need, without you having to enter them manually.

Feel free to navigate through your contentful dashboard and add a few posts; it will come in handy as we move on.

Finally, we need a way to access our space key and token without hardcoding their values.

To do so, create a __`.env`__ file, add your space key and token.

```
CONTENTFUL_SPACE=<your space key>
CONTENTFUL_TOKEN=<your token>
```

We need to do is allow our Next.Js application to read our `.env` file. To do so, modify your `next.config.js` file to the following:

{% gist https://gist.github.com/abedzantout/a958a98caa5f1f96109dc6894ca3fe58 %}

Make sure you install `dotenv-webpack` by running:
```npm install dotenv-webpack --save```

Great! Now you can safely access your env variables using `process.env.VARIABLE`.

Next, make sure you install contentful by running:
```npm install contentful```

We are now ready to start building our new application!

### Data Models
Let's define our first two models.

Within the `interfaces` folder, create two files:

__author.ts__
```typescript
// interfaces/author.ts
export type Author = {
  name: string;
  title: string;
  company: string;
  shortBio: string;
  email: string;
  twitter?: string;
};
```

__blog-post.ts__
```typescript
// interfaces/blog-post.ts
export type BlogPost = {
 title: string;
 slug: string;
 heroImage: any;
 description: string;
 body: any;
 author: Author;
 publishDate: Date;
};
```

Notice that in both cases, we are mapping the same data models we created in the Contentful CMS.

## Integrating Contentful API
Under your `core` folder, create __`contentful.ts`__ containing the following:

{% gist https://gist.github.com/abedzantout/469180a19b0e64f62159ac80344d2e01.js %}

What we've done here is creating a `ContentfulService` that connects to Contentful API, built the appropriate handlers to fetch the data and map it, so it is ready to be consumed.

## Implementing the shared components

To make our blog appealing, we need to implement and design a couple of elements that distinguish it from the rest.

Let's organize every component of our application its folder. For example, the card component and style sheet will be available under the card folder.

```
- shared
  - components
    - card
      - index.tsx
      - styles.css
    - meta
      - index.tsx
      - styles.css
...
```

I prefer this approach because it allows us to modularize our application into a more precise structure further.

Moreover, it gives us more flexibility in the future when we want to split a more significant component into a small, more compact one.

### Meta Tags

Let's start with our most important component, the meta tag component.

The meta tag component includes a __Next.Js Head__, which is a built-in component that allows you to update the `<head>` of your HTML page. 

Let's first define the tags we want to include in our blog.

We want to utilize the Open Graph tags (for facebook sharing), as well as the twitter tags, the description of the page and most importantly, the title. Including the page type is also important: we want to differentiate between a page and a blog post.
  
We also want to define whether the search engines should index the page or not.

All of the tags can be dynamic, depending on the page you are it. Having dynamic tags that change according to the page the user is on is excellent for SEO purposes.

Let's define our `Tag` model. Under the interfaces folder, create a __`tag.ts`__ containing the following:

{% gist https://gist.github.com/abedzantout/8f76bac3b8c409b1565df8909f409edf.js %}

Notice I also added two enums: the __PageType__ and __RobotsContent__.

This approach will allow us to easily add the page type and the robots tag into our meta tags while minimizing duplication and human error.

Under the `shared/components` folder, create the `index.tsx` file and include the following:

{% gist https://gist.github.com/abedzantout/133a0b65ff278b939aa3713379cb1759.js %}

To avoid duplicate tags in your `<head>`, you can use the `key` property, which guarantees that the tag is rendered only once.

### Layout

The layout component serves as a container across all pages of the application.

{% gist https://gist.github.com/abedzantout/3c1ad394bdb97ab5f73bde0ae3c99b4a.js %}

### Card

The card in our case displays the hero image of the blog, the title, description and the call to action. The call to action redirects the user to the blog post.

First, let's add some functions that help us to pass the dynamic URLs automatically.

Under the `core` folder, create a folder called `helper`, and include a __`helper.ts`__ file:

{% gist https://gist.github.com/abedzantout/8c187fa9817738857a9cd2592da63822.js %}

Next, under the `shared/components` folder, create a `card` folder and an __`index.tsx`__:

{% gist https://gist.github.com/abedzantout/7e9ed3fadd2b2997ca7034fa4382fef5.js %}

As a bonus, let's give it some extra style:

{% gist https://gist.github.com/abedzantout/a44f7048b40cb85ea96b262e4da1deb0.js %}

Don't forget to import the styles within your card's `index.tsx` file.
```import './styles.css'```

### Paginator
The paginator component helps the user to navigate across a large number of blog posts.

The paginator has a state that we need to maintain. We need to be able to tell the user on which page he or she is on, as well as display the page they are on in a visually pleasing way.

The paginator contains a two-way data binding: the user can navigate across pages, and if directly accessed the page through the URL, the paginator should display which page we are on.

{% gist https://gist.github.com/abedzantout/9b1e32ad0b4b7323c9b6bd42dfaadfad.js %}

Let's style it. Create a file called `styles.css` under the paginator folder:

{% gist https://gist.github.com/abedzantout/83ef11eab82da85351ee4fb1c579b060.js %}

## Our code structure so far

```
- core
  - contentful
- pages
  - index.tsx
- interfaces
  - author.ts
  - blog.ts
  - tag.ts
- shared
  - components
     - card
       - index.tsx
       - styles.css
     - layout
       - index.tsx
     - meta
       - index.tsx
     - paginator
       - index.tsx
       - styles.css
   - helpers
     - helper.ts
```
   
## Integrating the blog

The main page of the blog will include the cards, pagination, and filer components. There are a few things we need to load at the homepage.

We need to fetch all the tags, the total number of posts, skip number (for pagination purposes), the limit (number of posts to fetch per page), the page number, and the actual posts on that page.

All this can be done using the Contentful API we just created.

Under `pages/index.tsx`, let's update our `index` page:

{% gist https://gist.github.com/abedzantout/b1487c72c97c1a0c251eb34b049fb891.js %}

## Integrating the blog post

Under the pages folder, create a folder __`post`__ , and two files: __`index.tsx`__ and __`styles.css`__.

Let's add the appropriate functions to render the blog post under `post/index.tsx`:

{% gist https://gist.github.com/abedzantout/a9f1f5a57b434d2c05e97632e9aeaca3.js %}

Also, the styles:

{% gist https://gist.github.com/abedzantout/5b2b072080b2c937a74d2971cbd0b834.js %}

## Adding the meta tags
Integrating the meta tags deserve a section on their own.

Remember that we want the meta tags to be dynamic across different posts, but set to a default mode on the main page.

Our meta-tag component is flexible enough to handle all the meta-tags we throw in it.

There's one tag in particular that we need to take extra care for, the __`robots`__ tag.

The `robots` tag helps Search Engines to "know" if a particular page should be indexed or not.

To include multiple values in `robots` tag, let's build a helper function __concatenateStrings__ that concatenates them in a way that is easy for Search Engines Crawlers to parse.

```typescript
/*
 * turns an array of strings into a single string separated by a           
 * comma
export function concatenateStrings(...args: string[]) {
  return args.join(',');
}
```

### Default Tags

Next, let's include the default meta tags in a __`constants.ts`__ file `core` folder:

{% gist https://gist.github.com/abedzantout/7600bfce83cfa31b77946e4b14fe9bd7.js %}

We can include here any tags we need, export them and consume them on the right page.

Let's update our __Layout__ component to accommodate for the new tags.

{% gist https://gist.github.com/abedzantout/880825bf605fa1a168e040e8582a3986.js %}

And include the __tags__ input under `pages/index.tsx`:

```typescript
import {defaultMetaTags} from '../core/constants';
...
<Layout meta={defaultMetaTags}> // <-- added
...
</Layout>
...
```

### Post Specific Tags

The meta tags on the post are set dynamically. 

In order to do so, navigate to your `pages/post/index.tsx` and add the following to your ComponentFunction:

```typescript
const postMetaTags: MetaTags = {
    // you can set this dynamically later with proces.env
    canonical: `<your domain name>`, 
    description: `${props.article.description}`,
    image: `https:${props.article.heroImage.url}`,
    robots: `${RobotsContent.follow},${RobotsContent.index}`,
    title: `${props.article.title}`,
    type: PageType.article,
};

...
<Layout metaTags={postMetaTags}> // <- add this
...
</Layout>
...

```

Don't forget to include the right imports.

## Integrating Google Analytics

We want to integrate Google Analytics to gather some useful data from our blog.

For convenience, we want to track only in a production environment.

Within the `core` folder, create a __gtag.ts__ file that includes the following:

{% gist https://gist.github.com/abedzantout/7faac252b7b3050b8e60143e784a813d.js %}

The above functions allow us to communicate with Google Analytics and use it to track different user interactions with our blog.

First, let's inject the Google Analytics tag on every page of the application. To do so, create a __document.tsx__ file in the `pages` folder containing the following:

{% gist https://gist.github.com/abedzantout/948d5615e1cf582c01ee4b83458c8190.js %}

Here, we've injected the google-analytics tag in the head of every page, and override the default Document Component of Next.Js.

Create an ___app.tsx__ in the pages folder. We need to override the default App component provided by Next.Js.

{% gist 
 https://gist.github.com/abedzantout/4ea1598a078e70d1dd456185ec076d4b.js %}

By listening to the router events, we can record them in Google Analytics so we can analyze them in the future.

## Automatically generating a sitemap

Every website and blog needs a sitemap to help optimize their SEOs.

What we need is an automatic post-export process that generates the sitemap and injects it into the production build.

We need to detect every new blog post that we publish and update the sitemap.

Let's update our `next.config.js` to include the right paths to export:

{% gist https://gist.github.com/abedzantout/fe8aab9db7653e44d5dac16383343bf0.js %}

Under the `utils` folder, add the following script:

{% gist https://gist.github.com/abedzantout/0c87688db325bb88dd9db55046d7cdc4.js %}

Add another file `post-export.js` that imports the functionality from `sitemap.js` and use it in the post-build.

{% gist https://gist.github.com/abedzantout/8c4eb49bb4bffdf31abfbdf2acdae39d.js %}

Add `DOMAIN_NAME=<your domain name>` to your`.env` file.

The script automatically reads all the posts fetched by Next.Js, build the sitemap, and injects it into the build folder (the __out__ folder in our case).

The last step is to run the script after every build and export automatically.

Let us add the following command to __package.json__:

```
"postexport": "node utils/post-export.js"
```

We are  ready to set up the website deployment.

## Deploying to Netlify
Deploying a Next.Js project is very easy. You can deploy the project to Now, Netlify or any provider that you prefer.

However, for our case, we will deploy the blog to Netlify.

Go to your Netlify dashboard, create a site and connect it to your Github repo (or upload the code using their upload form).

Set the deployment command to:

```
npm run export
```

Set the directory to "out".

Also, make sure you connect your Contentful account to Netlify and choose your space. Netlify takes care of the rest. This approach has many advantages, mainly because we are building a static website, so every change in the CMS needs to be reflected on the website. You also do not need to set your environment variables.

You are done!

## Final words
Congratulations! You have successfully created a neat blog, with markdown support, integrated Continous Integration and Continous Delivery, and you are ready to launch it to the world!

---

## Ready-made Starter Kit
To make things easier for you, we have created a [starter kit](https://github.com/techhiveIO/nextjs-static-starter-kit "NextJs Static Starter Kit") that includes a template and can get you bootstrapped in no time. We even created a script to create your Contentful Content Data automatically. Feel free to submit your PRs, issues and don't forget to star our repo.