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:
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:
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.
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.
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:
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
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.
Make sure you install the contentful-cli and contentful-migration before moving to the next step:
npm install contentful-cli contentful-migration --save-dev
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"
}
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:
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!
Let's define our first two models.
Within the interfaces
folder, create two files:
author.ts
// interfaces/author.ts
export type Author = {
name: string;
title: string;
company: string;
shortBio: string;
email: string;
twitter?: string;
};
blog-post.ts
// 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.
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.
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.
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:
- 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
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:
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:
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(',');
}
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
...
</Layout>
...
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
...
</Layout>
...
Don't forget to include the right imports.
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.
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"
We are ready to set up the website deployment.
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!
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!
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.