Avatar

How I Integrated MDX Blog in My Portfolio

When I was building my portfolio, I wanted to add a blog section where I could write about my experiences and learnings. After some research, I decided to use MDX (Markdown with JSX) because it lets me write in simple Markdown while still being able to use React components when I need them. Here's how I set it all up using next-mdx-remote.

Why MDX and next-mdx-remote?

I chose MDX because it's the best of both worlds - I can write blog posts in Markdown (which is super easy), but I can also drop in React components whenever I want to add something interactive or custom.

I went with next-mdx-remote specifically because it works great with Next.js 16's App Router and React Server Components. It lets me store my blog posts as simple .mdx files and compile them on the server side, which is perfect for my setup.

Installing the Package

First things first, I installed the package:

npm install next-mdx-remote

That's it! Just one package and I was ready to go.

Organizing My Blog Posts

I decided to keep all my blog posts in a simple folder structure. I created an app/data folder and put all my .mdx files there. Each file is named with a slug (like getting-started-with-nextjs.mdx or intro.mdx).

Each blog post starts with frontmatter (the metadata at the top):

---
title: "My Blog Post Title"
description: "A short description of the post"
date: "2025-01-27"
tags:
  - Next.js
  - React
---

Then I just write my content in Markdown below that. Super simple!

Creating Helper Functions

I created a utility file at app/utils/mdx.ts that handles reading and compiling my MDX files. This keeps all the MDX logic in one place.

The main function I use is getSingleBlog, which takes a slug and returns the compiled content and frontmatter:

export async function getSingleBlog(slug: string) {
  const singleBlog = await fs.readFile(
    path.join(process.cwd(), "app/data", `${slug}.mdx`),
    "utf-8"
  );

  const { content, frontmatter } = await compileMDX({
    source: singleBlog,
    options: { parseFrontmatter: true },
  });

  return { content, frontmatter };
}

I also have getAllBlogs() which reads all the MDX files in the data folder and returns their frontmatter. This is what I use to show the list of all blog posts on my blog index page.

Setting Up the Blog Post Page

I created a dynamic route at app/blog/[slug]/page.tsx that handles individual blog posts. This is where the magic happens.

The page is a Server Component (which is the default in Next.js 16), so I can directly use await to fetch the blog content:

export default async function SingleBlogPage({ params }) {
  const { slug } = await params;
  const blog = await getSingleBlog(slug);

  if (!blog) {
    redirect("/blog");
  }

  const { content, frontmatter } = blog;
  return (
    <div className="prose">
      {content}
    </div>
  );
}

The content that comes back from compileMDX is already a React component, so I can just render it directly! No need for MDXRemote wrapper in this case since I'm using the RSC version.

Styling the Blog Posts

For styling, I'm using Tailwind's Typography plugin. I just wrap the content in a prose class and it automatically styles all the Markdown elements (headings, paragraphs, lists, code blocks, etc.) to look nice. I also added dark mode support with dark:prose-invert.

The Blog Index Page

For the blog listing page at app/blog/page.tsx, I use getAllBlogs() to get all the posts and display them in a nice list. Each post shows the title, date, and a truncated description, and clicking on one takes you to the full post.

What I Like About This Setup

The best part about this setup is how simple it is. I just write Markdown files, drop them in the app/data folder, and they automatically show up on my blog. No database needed, no CMS to manage - just files.

Plus, since everything compiles on the server, the blog posts are super fast to load. And if I ever want to add a custom React component to a blog post, I can just import it and use it right in the Markdown. It's that flexible!

Wrapping Up

That's basically it! The whole setup is pretty straightforward once you get the hang of it. The key pieces are:

  1. Install next-mdx-remote
  2. Store your blog posts as .mdx files with frontmatter
  3. Create helper functions to read and compile the MDX
  4. Set up a dynamic route to render individual posts
  5. Style with Tailwind Typography (or whatever you prefer)

If you're building a portfolio or personal site and want to add a blog, I'd definitely recommend giving MDX a try. It's made writing and maintaining my blog posts a breeze!

Built with Love by Shekhar