Create Blog with MDX and Contentlayer

M. K. Bughowi

August 2022, 3

6 min read

Create Blog with MDX and Contentlayer

Blog is a media where we can share information, thoughts, or something else. Just like this blog, here I share my thoughs about some Web Development stuff and also programming in general. When building a personal blog, there are so many ways to store our article and serve it to visitor. We can use CMS (Content Management System) platform to manage contents. Alternatively, we can write contents in MDX format, store it in github repo, and manage it with contentlayer. Maybe some of you is not familiar with MDX and Contentlayer. So, in this article I’m gonna show you what is MDX and Contentlayer and how we can build a personal blog with this two things.

MDX

First, let’s get to know about MDX. Do you ever know about readme.md file when you initialize a repository in github? Readme.md allows us to write documentation for github repository. Readme.md has .md file extension. An .md file is a text file created using one of several possible dialects of the Markdown language. It is saved in plain text format but includes inline symbols that define how to format the text (e.g. bold, indentations, headers, table formatting). MD files are designed for authoring plain text documentation that can be easily converted to HTML.

For example, if i write sample.md file likes below

# This is heading 1
This is how we write paragraph in md files.
- This is a list
- List one
- List two
Or you can make a **bold** text, _italic_, or **_both_**

Then, converted to HTML, it will looks like this

<h1>This is heading 1</h1>
<p>This is how we write paragraph in md files.</p>
<ul>
<li>This is a list</li>
<li>List one</li>
<li>List two</li>
</ul>
<p>Or you can make a <strong>bold</strong> text, <em>italic</em>, or <strong><em>both</strong></em></p>

And that’s MD all about. Basically, MDX is same as MD but it has more features. MDX allows you to use JSX in your markdown content. You can import components, such as interactive charts or alerts, and embed them within your content. This makes writing long-form content with components a blast.

import { Chart } from './snowfall.js';
export const year = 2018;
# Last year’s snowfall
In {year}, the snowfall was above average.
It was followed by a warm spring which caused
flood conditions in many of the nearby rivers.
<Chart year={year} color="#fcb32c" />

Contentlayer

Contentlayer is a content preprocessor that validates and transforms your content into type-safe JSON you can easily import into your application. Contentlayer significantly reducing the boilerplate and external tools required to effectively integrate MDX content with the rest of your app. I have been using Contentlayer for this blog and really enjoy using it. Without any futher, let’s jump to add contentlayer to generate our MDX content.

Install and Configure Contentlayer

Install contentlayer packages inside Next.js project.

Terminal window
npm install contentlayer next-contentlayer

Next, we need to configure our next.config.js file to hook Contentlayer into the next dev and next build processes.

next.config.js
const { withContentlayer } = require('next-contentlayer');
module.exports = withContentlayer({
// your next config goes here
});

Then add the following code inside tsconfig.json (if using TypeScript) or jsconfig.json file.

tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"contentlayer/generated": ["./.contentlayer/generated"]
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
},
"include": ["next-env.d.ts", "**/*.tsx", "**/*.ts", ".contentlayer/generated"]
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

Define Document Type

Creat a schema for your MDX content. To do this, you need to create a new file called contentlayer.config.ts in root folder of Next.js project. Here I create a schema for my blog post content.

contentlayer.config.ts
import { defineDocumentType, makeSource } from 'contentlayer/source-files';
const Post = defineDocumentType(() => ({
name: 'Post',
filePathPattern: `blog/*.mdx`,
contentType: 'mdx',
fields: {
title: {
type: 'string',
description: 'The title of the post',
required: true,
},
description: {
type: 'string',
required: true,
},
date: {
type: 'date',
description: 'The date of the post',
required: true,
},
categories: {
type: 'list',
of: Category,
},
},
computedFields: {
url: {
type: 'string',
resolve: (post) => `/${post._raw.flattenedPath}`,
},
},
}));
export default makeSource({
documentTypes: [Post],
});

This configuration will generate a single document type named Post. These documents are expected to be .mdx files that lives within a blog directory in my project. And data objects generated from these files will have the following properties:

  • title. Title for blog post (in string). Pulled from the file’s frontmatter.
  • description. Description for blog post.
  • date. JavaScript Date object, pulled from the file’s frontmatter.
  • categories. Post categories. This property is a list of Category type.

If you see in categories property, I define it as a list of Category. Category itself is a nested type that I define with defineNestedType().

const Category = defineNestedType(() => ({
name: 'Category',
fields: {
name: { type: 'string', required: true },
},
}));

You also can add more than one document type. To do that, you just need create a new schema like what you did when creating a post schema. Then, in makeSource, you export this two schema inside documentTypes property.

export default makeSource({
contentDirPath: 'contents',
documentTypes: [Post, Project],
});

Lastly, create a “hello world” post inside the configured directory for your post document e.g. contentDirPath + filePathPattern = /contents/blog

---
title: Lorem Ipsum
description: Excepteur consequat nostrud esse esse fugiat dolore.
Reprehenderit occaecat.
date: 2021-12-24
categories: ["contentlayer", "next.js"]
---
Ullamco et nostrud magna commodo nostrud occaecat quis pariatur id ipsum. Ipsum
consequat enim id excepteur consequat nostrud esse esse fugiat dolore.
Reprehenderit occaecat exercitation non cupidatat in eiusmod laborum ex eu
fugiat aute culpa pariatur. Irure elit proident consequat veniam minim ipsum ex
pariatur.

Get All Posts

Now we can tie it all together by bringing the Post data into our pages. But we need a library to help us format the date and get all post sorted by newest date.

Terminal window
npm install date-fns

Let’s get our Post inside getStaticProps() function. Then display post title, description, and date in our page component.

import { compareDesc, format, parseISO } from 'date-fns';
import { allPosts, Post } from 'contentlayer/generated';
import { GetStaticProps } from 'next'; // Add this import statement
const Home = ({ posts }: { posts: Post[] }) => {
return (
<div>
{posts.map((post, key) => (
<div key={key}>
<h1>{post.title}</h1>
<p>{post.description}</p>
<p>{format(parseISO(post.date), 'LLLL d, yyyy')}</p>
</div>
))}
</div>
);
};
export default Home;
export const getStaticProps: GetStaticProps = async () => {
const posts = allPosts.sort((a, b) => {
return compareDesc(new Date(a.date), new Date(b.date));
});
return {
props: { posts },
};
};
Edit this page Tweet this article