In this guide, we'll explore how to create a simple, yet powerful blog using Next.js in combination with MDX. A perfect solution for developers and small teams that want to start their blog-writing journey.
We’ll be using Next.js (v14+) configured with Tailwind CSS and TypeScript as a starting point.
Key Features:
-
Static MDX Development: Using static MDX files makes it easy to start your blog without connecting to third party API's, CDN's or a database. This approach is ideal for simple applications.
-
Rich Metadata with Front Matter: Use the front matter in your MDX files to store essential post metadata. From tags and categories to author information and descriptions, it can be easily parsed using the gray-matter package.
-
Stylish Typography with Tailwind CSS: Apply some default styling to your blog using the Tailwind CSS Typography plugin, making it look like great instantly.
-
Code Highlighting: Use the Rehype Highlight plugin to add syntax highlighting to your code snippets in your desired style.
-
SEO Optimized: Implement some Next.js SEO best practices to make your blog more discoverable.
Combining these elements creates a great starting point for your blog that's easy to customize and reuse in all your projects.
File Structure
> app
> blog
> (components)
- PostCard.tsx
- ...
> (posts)
- blog-1-slug.mdx
- blog-2-slug.mdx
- ...
> [slug]
- page.tsx // Blog details page
- page.tsx // Blogs overview (list) page
TODO Check the GitHub repo to see the file content.
Metadata using Gray-matter
Each MDX file contains some front-matter that acts as metadata for our posts. For this examples, we’ll use the following values:
---
image: "/link/to/you/banner/image.webp"
imageThumbnail: "/link/to/you/banner/image-thumb.webp"
title: "Title of Your Post"
slug: "slug-of-your-post"
description: "Short description of the blog post, visible in the preview card."
tags: ["Tag 1", "Tag 2", "Tag 3"]
createdAt: "2024-10-09"
readingTime: 8
---
Some extra info:
- imageThumbnail: The same image as the ‘image’ field, but just a smaller size to improve loading speed. This image is used in the post preview cards.
- slug: This should be the same as your filename! E.g. slug-of-your-post.mdx
To extract this front-matter data from your .mdx files, install the gray-matter package.
npm install gray-matter
Use this package to read the .mdx files and separate the markdown (content) from the front-matter (data).
import fs from "fs";
import path from "path";
import matter from "gray-matter";
...
// Get all MDX files in the posts folder
const allFiles = fs.readdirSync("app/blog/(posts)/");
const mdxFiles = allFiles.filter((file) => file.endsWith(".mdx"));
// Extract the front-matter data from each file
const posts = mdxFiles.map((fileName) => {
const fileContent = fs.readFileSync(`${folder}/${fileName}`, "utf8");
const { content, data } = matter(fileContent);
// Handle the data here
console.log(data.title);
});
...
Setup MDX in Next.js
Here, you can find the official documentation on how to add MDX support to your Next.js project.
Next Image Component
Next provides an Image component that handles some image optimizations out-of-the-box. To use it in our blogs, add it to the mdx-components.tsx file.
import type { MDXComponents } from "mdx/types";
import Image, { ImageProps } from "next/image";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
img: (props) => (
<Image
sizes="100vw"
style={{ width: "100%", height: "auto" }}
{...(props as ImageProps)}
/>
),
...components,
};
}
Remote MDX
Using remote MDX enables us to store our .mdx files in a separate folder instead of working with MDX pages. This approach is useful if you want to keep your markdown files separate from your Next.js pages. To do this, we use the next-mdx-remote package.
npm install next-mdx-remote
Use the content of your .mdx files as the source prop in the MDXRemote component.
import matter from "gray-matter";
import { MDXRemote } from "next-mdx-remote/rsc";
import { useMDXComponents as MDXComponents } from "@/mdx-components";
...
const { content, data } = matter(fileContent);
<article>
<MDXRemote
source={content}
components={MDXComponents({})}
/>
</article>
...
Styling Using Tailwind CSS Typography
The MDX remote package renders HTML from our markdown. To style this, we’ll use the Tailwind CSS Typography plugin, which provides a set of prose classes that add beautiful typographic defaults to the vanilla markdown HTML. Follow the instructions to install and configure.
If you’re not happy with how it looks, you can modify the styling of all elements.
<article class="prose prose-img:rounded-xl prose-headings:underline prose-a:text-blue-600">
{{ markdown }}
</article>
Code Highlighting
As you’re building a blog yourself, I’m sure you’ll have some code examples in your blog posts. By default, the Tailwind plugin with the prose class adds some styling to code blocks, but without any highlighting. Pretty boring if you ask me.
There are different ways to implement syntax highlighting, but we’ll use the Rehype Highlight plugin to apply syntax highlighting to our code snippets.
First, install the package.
npm install rehype-highlight
Now, add it to the options of the MDXRemote component. By setting detect to true, code blocks without a specified code language are also highlighted.
// app/blog/[slug]/page.tsx
import rehypeHighlight from 'rehype-highlight';
const options = {
mdxOptions: {
remarkPlugins: [],
rehypePlugins: [[rehypeHighlight, { detect: true }]],
}
}
...
<MDXRemote
source={content}
components={MDXComponents({})}
options={options as any}
/>
...
We still need to add a specific style so the plugin knows how to actually highlight the code. To do this:
-
Check the highlight.js demo and look for a theme you like.
-
Download the .css file of your chosen theme here.
-
Copy it into your repo and import it in the page used to display your MDX content.
// app/blog/posts/[slug]/page.tsx import "@/styles/chosen-theme.css"
-
When using Tailwind CSS with prose for styling, it will add some default padding to your code blocks with the pre tag. To make the background color consistent, edit you .css file to also apply it to the pre class:
/* Turn this */ .hljs { color: #c9d1d9; background: #0d1117; } /* Into this */ pre:has(.hljs), .hljs { color: #c9d1d9; background: #0d1117; }
Markdown vs. MDX
MDX is a superset of markdown that lets you write JSX or TSX directly in your markdown files. It is a powerful way to add dynamic interactivity and embed React components within your content.
Feature | Markdown | MDX |
---|---|---|
React components | ❌ Not supported | ✅ Can import and use React components |
JavaScript expressions | ❌ Not supported | ✅ Supports JavaScript expressions |
Dynamic content | ❌ Static content only | ✅ Can include dynamic content |
Interactivity | ❌ Static content only | ✅ Can include interactive elements |
When using MDX, you can still just use markdown syntax, but you can optionally add some interactivity to your content.
Pro's and Con's of This Method
Using static MDX files makes it easy to start your blog without connecting to third party API's, CDN's or a database. This approach is ideal for simple applications, but also has some downsides.
Pros
- Simple setup and maintenance
- Version control friendly (can use Git for content management)
- Fast page loads due to static content
- Improved security (no database to hack)
Cons
- Not easy to collaborate on content creation
- Harder to create/edit blog posts without a user-friendly interface
- Always need to rebuild your site to see your new posts or changes
- Difficult to implement user-generated content (comments, etc.)