Minimal blog with Next.js, Tailwind CSS, and markdown (MDX)

I decided to add a blog to my website. Here’s how I built it for my first article. I want something minimal, fast, and SEO-friendly.


Next.js is my favorite tool for making websites. With a concept of zero-configuration, a lot of features (image optimization, SSG and SSR, incremental static regeneration, routing system, etc.) it lets you focus exclusively on your product and building it pleasantly. Tailwind CSS is a utility-first CSS framework that builds styles super quickly. MDX is "Markdown for the component era". It lets you write JSX in your Markdown documents.

For this article, I will assume you are already familiar with JavaScript and React.

Project setup

To create a Next.js project with Tailwind CSS, in your terminal, you can use

npx create-next-app -e with-tailwindcss my-project
cd my-project

Then, npm run dev, and you’re good. 😎

If you want do start a project with manual setup, you can reach the Next.js doc. For adding Tailwind CSS to an existing Next.js project, you can also check the doc.


Next.js has a /pages folder where every page of our website goes. For our blog, we will have



With MDX, we can mixes markdown with JavaScript and JSX components. It's such a powerful tool for react developers. If you don't know how to write markdown, there are a lot of guides, you can check this one for example. You can try to write MDX code. For example, we can add:

# Example of JSX embedded in Markdown
<div style={{ padding: '16px', backgroundColor: 'lightpink' }}>
<h3>This is JSX</h3>

Why markdown?

Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents. It's a very used language in the developer area, in our next project we have a, it's markdown. It's easy to use, fast and multi-platform.

Alternatively, we can also use a CMS (Content Management System). There is a lot (like prismic, strapi, etc.) and generally, they offer a free plan for solo users. But if like me, you're a developer and you're managing your blog solo, I'd personally prefer MDX.

MDX + Next.js?

There are different ways to use MDX with Next.js. I would use @next/mdx.

In your terminal:

npm install --save @next/mdx @mdx-js/loader

To treat MDX as pages, we will add in our next.config.js

const withMDX = require("@next/mdx")({
extension: /\.(md|mdx)$/,
module.exports = withMDX({
pageExtensions: ["js", "jsx", "md", "mdx"],

Now, if you add first-post.mdx in /blog and write some markdown there, like:

# It's my first blog post.
## Some title
Some text.

Run your Next.js project with npm run dev go to http://localhost:3000/blog/first-post and here we go, we can see our first page.

Tailwind CSS + MDX?

With MDX, we can write jsx, and so we can add css class, let add some padding and margin to our blog post.

<article className="py-12 px-6 mx-auto">
# It's my first blog post.
## Some title
Some text.

For fonts, Tailwind CSS provides a pluging to style our HTML elements generated from mdx. After installing the plugin, all we have to to is add prose class <article className="prose ...">

Fetch and dynamic import blog posts

Right now, we can write a page in mdx with some Tailwind CSS styling. Now, we want a page with all our blog posts. For this, we will firstly add metadata in our posts. Metadata will allow us to describe the contents of our mdx file.

In /blog/first-post.mdx:

export const metadata = {
title: "Minimal blog with Next.js, Tailwind CSS and markdown!",
"How to build a minimal blog with Next.js, Tailwind CSS and MDX?",
date: "2021-07-23",

To build our page, we need to import:

import fs from "fs";
import path from "path";
import Link from "next/link";

fs will let us interact with the file system and path provides utilities for working with file and directory paths. Link will let the client navigate through blog post.

Then, we will use getStaticProps to fetch all our blog posts.

export const getStaticProps = async () => {
const postDirectory = path.join(process.cwd(), "pages/blog");
const postFilenames = fs
.filter((path) => /\.mdx?$/.test(path));
const posts = => {
return {
slug: file.replace(".mdx", ""),
return {
props: {

What did we do here?

  • With getStaticProps the HTML is generated at build time and will be reused on each request. Here, it will let us fetch blogs Metadata and pass it in as props in our page.
  • postDirectory we get our directory where all our posts are
  • With postFilenames we get an array of .mdx files in our blog directory
  • Then we mapping our array of files in posts. ...require(`./${file}`).metadata will let us import our metadas and get the slug with file.replace(".mdx", "")
  • Finally, we return posts, we will then use them in our page

We get all our blog posts, we need to pass it to the client so he can navigate in our blog. In the same file we can do:

const Blog = ({ posts }) => {
return (
<section className="mx-auto min-h-screen max-w-screen-md px-6 py-36">
{ => {
return (
<li key={post.slug} className="mb-6">
<Link href={`/blog/${post.slug}`}>
<span className="mx-1">-</span>
<span className="font-semibold">{post.title}</span>

  • Access posts from our getStaticProps
  • Style with Tailwind CSS
  • Map thought posts, for each post we render some jsx
  • Use Link to do client-side navigation and recover our metadas with date and title

Go to http://localhost:3000/blog, TADA 🎉, user can navigate into all our blog posts.


Vercel is the company behind Next.js. It feels like magic to deploy a webapp with Vercel, it's easy, fast, comes with a lot of integrations and it's free for personal projects! You can get how to deploy a Next.js app with vercel in Next.js doc.

What's next?

Now we have a minimal blog built with Next.js, Tailwind CSS, and MDX. We can go further and improve our blog a lot with things like:

  • SEO
  • Dark mode
  • Layout component
  • Image handling

This is my first blog post, hope you liked it. You can dm me on Twitter if you have any questions. Thanks for reading!

Published: 7/23/2021

Join my newsletter to stay updated about the latest things I've created and discover the great finds I've come across on the internet.