DEV Community

Cover image for How to build a powerful blog with NextJS and Contentful
Abdul Rahman Zantout for Techhive.IO

Posted on • Updated on • Originally published at

How to build a powerful blog with NextJS and Contentful

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.


You can add other requirements, but this should get you started in thinking about the design.


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


npm install -g create-next-app
create-next-app my-next-blog && cd my-next-blog
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

Setting up Contentful

Contentful 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:

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"
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

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:


// interfaces/author.ts
export type Author = {
  name: string;
  title: string;
  company: string;
  shortBio: string;
  email: string;
  twitter?: string;
Enter fullscreen mode Exit fullscreen mode


// interfaces/blog-post.ts
export type BlogPost = {
 title: string;
 slug: string;
 heroImage: any;
 description: string;
 body: any;
 author: Author;
 publishDate: Date;
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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:

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


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


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:

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

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

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

import './styles.css'


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.

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

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
Enter fullscreen mode Exit fullscreen mode

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:

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:

Also, the styles:

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.

 * turns an array of strings into a single string separated by a           
 * comma
export function concatenateStrings(...args: string[]) {
  return args.join(',');
Enter fullscreen mode Exit fullscreen mode

Default Tags

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

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.

And include the tags input under pages/index.tsx:

import {defaultMetaTags} from '../core/constants';
<Layout meta={defaultMetaTags}> // <-- added
Enter fullscreen mode Exit fullscreen mode

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:

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

Enter fullscreen mode Exit fullscreen mode

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:

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:

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.

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:

Under the utils folder, add the following script:

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

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 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.

Top comments (0)