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.
Tools
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-projectcd 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.
Structure
Next.js has a /pages
folder where every page of our website goes.
For our blog, we will have
pages/ blog/ index.js my-first-blogpost.mdx
MDX 🔥
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></div>
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 README.md
, 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 titleSome 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.</article>
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!", description: "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 .readdirSync(postDirectory) .filter((path) => /\.mdx?$/.test(path)); const posts = postFilenames.map((file) => { return { ...require(`./${file}`).metadata, slug: file.replace(".mdx", ""), }; }); return { props: { posts, }, };};
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 withfile.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"> <ul> {posts.map((post) => { return ( <li key={post.slug} className="mb-6"> <Link href={`/blog/${post.slug}`}> <div> <span>{post.datePublished}</span> <span className="mx-1">-</span> <span className="font-semibold">{post.title}</span> </div> </Link> </li> ); })} </ul> </section> );};
- 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 withdate
andtitle
Go to http://localhost:3000/blog
, TADA 🎉, user can navigate into all our blog posts.
Hosting
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!
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.