Logo Milan Meurrens

Coding

How to create an MDX blog using Next.js

Oct 9

12 min read

How to create an MDX blog using Next.js

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:

  1. 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.

  2. 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.

  3. Stylish Typography with Tailwind CSS: Apply some default styling to your blog using the Tailwind CSS Typography plugin, making it look like great instantly.

  4. Code Highlighting: Use the Rehype Highlight plugin to add syntax highlighting to your code snippets in your desired style.

  5. 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:

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:

  1. Check the highlight.js demo and look for a theme you like.

  2. Download the .css file of your chosen theme here.

  3. 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"
    
  4. 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.

FeatureMarkdownMDX
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

Cons