How to build a blog with Next.js and MDX
Using Next.js to build a blog is very easy!

Aayush Bharti
Preface
The packages to be used:
- next Next.js framework
- next-mdx-remote handling and loading mdx content
- gray-matter parse the front matter in markdown
How to create a blog
First, we create the Next.js project with the following command:
yarn create next-app nextjs-mdx-blog
Next, create the following file structure:
components/layout.jsx
- Wrap all components in a container (optional, just the style)data/blog/*.mdx
- Blog Articleslib/format-date.js
- Format the date asMM DD, YYYY
pages/blog/[slug].jsx
- Article page, using dynamic routes
How to handle Markdown files
First, const root
- the root directory, and the process.cwd()
method returns
the current working directory of the Node.js process.
const root = process.cwd()
Next, create the POSTS_PATH
constant for the path where the article files are
stored:
import path from "path"
const POSTS_PATH = path.join(root, "data", "blog")
// Output: A:/nextjs-mdx-blog/data/blog
Then use fs
to read the contents of that directory (all the file names under
data/blog
):
import fs from "fs"
export const allSlugs = fs.readdirSync(POSTS_PATH)
// Output: ['markdown.mdx', 'nextjs.mdx', 'react.mdx']
Now, write a function to remove the file extension (useful later):
export const formatSlug = slug => slug.replace(/\.mdx$/, "")
/**
* Example: formatSlug('markdown.mdx')
* Output: 'markdown'
*/
The next step is to get the article content by slug:
export const getPostBySlug = async slug => {
const postFilePath = path.join(POSTS_PATH, `${slug}.mdx`)
// Output: A:/nextjs-mdx-blog/data/blog/slug.mdx
const source = fs.readFileSync(postFilePath)
const { content, data } = matter(source)
/*
* Example:
* ---
* title: Hello
* slug: home
* ---
* <h1>Hello world!</h1>
*
* Return:
* {
* content: '<h1>Hello world!</h1>',
* data: {
* title: 'Hello',
* slug: 'home'
* }
* }
*/
const mdxSource = await serialize(content)
const frontMatter = {
...data,
slug,
}
return {
source: mdxSource,
frontMatter,
}
}
Get all blog posts
You can get all the articles to be displayed on the homepage:
export const getAllPosts = () => {
const frontMatter = []
allSlugs.forEach(slug => {
const source = fs.readFileSync(path.join(POSTS_PATH, slug), "utf-8")
const { data } = matter(source)
frontMatter.push({
...data,
slug: formatSlug(slug),
date: new Date(data.date).toISOString(),
})
})
return frontMatter.sort((a, b) => dateSortDesc(a.date, b.date))
}
const dateSortDesc = (a, b) => {
if (a > b) return -1
if (a < b) return 1
return 0
}
Formatting Date
export const formatDate = date =>
new Date(date).toLocaleDateString("en", {
year: "numeric",
month: "long",
day: "numeric",
})
/*
* formatDate('2022-08-21T00:00:00Z')
* Output: 'August 21, 2022'
*/
Home Page
import { formatDate } from "../lib/format-date"
import { getAllPosts } from "../lib/mdx"
import Link from "next/link"
export default function Home({ posts }) {
return (
<>
<h1 className="mb-8 text-6xl font-bold">Blog</h1>
<hr className="my-8" />
<ul className="flex flex-col gap-3">
{posts.map(({ slug, title, summary, date }) => (
<li key={slug}>
<Link href={`/blog/${slug}`}>
<a className="block rounded-lg border border-solid border-gray-300 p-6 shadow-md">
<div className="flex justify-between">
<h2>{title}</h2>
<time dateTime={date}>{formatDate(date)}</time>
</div>
<p className="mt-4">{summary}</p>
</a>
</Link>
</li>
))}
</ul>
</>
)
}
// Use getStaticProps to get all articles
export const getStaticProps = async () => {
const posts = getAllPosts()
return {
props: {
posts,
},
}
}
Article Page
import { formatDate } from "../../lib/format-date"
import { allSlugs, formatSlug, getPostBySlug } from "../../lib/mdx"
import { MDXRemote } from "next-mdx-remote"
export default function Blog({ post }) {
const { title, date } = post.frontMatter
return (
<div>
<h1 className="mb-2 text-6xl font-bold">{title}</h1>
<time dateTime={date} className="text-lg font-medium">
{formatDate(date)}
</time>
<hr className="my-8" />
<article className="prose max-w-none">
<MDXRemote {...post.source} />
</article>
</div>
)
}
export const getStaticProps = async ({ params }) => {
const post = await getPostBySlug(params.slug)
return {
props: {
post,
},
}
}
export const getStaticPaths = async () => {
const paths = allSlugs.map(slug => ({
params: {
slug: formatSlug(slug),
},
}))
return {
paths,
fallback: false,
}
}