_

6 min read

Why I Chose MDX for My Next.js Portfolio Blog

When I started building my portfolio website, I had a very simple goal: I wanted a place where I could write and publish content without turning my personal site into a mini CMS project. I did not want dashboards, admin panels, or a content setup that felt heavier than the blog itself. I just wanted to write, commit, and ship.

That is when I ran into MDX, and it immediately felt like the right tool for this project.

MDX and Next.js cover art used in this blog post

And yes, this article is also written in MDX 🙂 That feels very on-brand.

What MDX Actually Is

MDX is Markdown with React components inside it. That sounds like a small upgrade, but it changes a lot.

You still write content in a simple Markdown-like format, but now you can also import components, render custom UI, use local assets, and treat an article like a real part of your app instead of a disconnected text file.

Article.mdx
import { Tag } from "@/components/Tag";

# My article title

Here is a regular Markdown paragraph.

<Tag label="Built with React" />

You can even import it as component in regular codebase:

App.tsx
import Article from "./Article.mdx";

export default function App() {
  return <Article />;
}

For me, that was the whole point. I wanted writing to stay simple, but I also wanted more than plain Markdown could offer.

Why I Chose It for This Portfolio

This portfolio is built with React, Next.js App Router, TypeScript, Tailwind CSS, and local content files. In that setup, MDX feels very natural. It gives me a few things I care about:

  • I can write blog posts like plain text.
  • I can keep every post in the repo right next to the code.
  • I can import images from the same folder as the article.
  • I can render React components inside the post when I need them.
  • I do not need a separate CMS just to publish a post.

That last point mattered the most. This is a personal website, not a publishing platform with editors, workflows, and content approvals. For this kind of project, MDX feels light, practical, and honestly a lot more enjoyable.

How to Set Up MDX in Your Next.js Project

The setup here is intentionally small. First, I installed the MDX packages for Next.js, plus two small plugins:

terminal
npm install rehype-slug rehype-starry-night
npm install -D @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

Then I configured next.config.ts to support .mdx files:

next.config.ts
import createMDX from "@next/mdx";

const nextConfig = {
  pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
};

const withMDX = createMDX({
  options: {
    remarkPlugins: [],
    rehypePlugins: ["rehype-starry-night", "rehype-slug"],
  },
});

export default withMDX(nextConfig);

About the Plugins, very briefly:

  • rehype-starry-night gives syntax highlighting for code blocks.
  • rehype-slug adds IDs to headings, which helps the table of contents and anchor links work properly.

That is enough for this project. No plugin collection addiction required.

One More Thing That Makes It Feel Polished

Another piece I really like is mdx-components.tsx.

mdx-components.tsx
const components: MDXComponents = {
  h2: withClassName(
    "h2",
    "text-main mt-8 mb-3 text-2xl font-medium tracking-tight scroll-mt-24"
  ),
  h3: withClassName(
    "h3",
    "text-main mt-6 mb-2 text-xl font-medium tracking-tight scroll-mt-24"
  ),
  p: withClassName("p", "text-secondary indent-4 mb-4 leading-relaxed"),
  ul: withClassName("ul", "text-secondary mb-4 list-disc pl-5"),
  a: withClassName(
    "a",
    "text-main underline underline-offset-2"
  ),
  img: withClassName("img", "my-4 mx-auto block rounded-xl max-w-full"),
};

This file maps standard Markdown elements like headings, paragraphs, lists, links, and images to the styles I want across the whole blog. So instead of styling every article manually, I define that once and keep writing.

What a Post Looks Like in This Repo

In this project, every post lives in its own folder inside blog/, and that folder contains the article file plus local assets.

That means the content and images stay together, which is a very nice little quality-of-life detail.

blog/nextjs_mdx_article/markdown.mdx
import { CodeBlock } from "@/components/CodeBlock";
import cover from "./assets/logo.png";

export const metadata = {
  title: "Why I Chose MDX for My Next.js Portfolio Blog",
  description:
    "How MDX became the simplest way for me to write blog content in my React and Next.js portfolio project.",
  readTime: "6 min read",
  tags: ["MDX", "Next.js", "React"],
  image: cover.src,
};

# Why I Chose MDX for My Next.js Portfolio Blog

Bla bla bla...

Then inside the blog page, we can import the MDX file directly, render it, and reuse its metadata for SEO.

app/blog/[slug]/page.tsx
export default async function BlogPostPage({ params }) {
  const { default: ArticleContent, metadata } = await import(
    `@blog/${params.slug}/markdown.mdx`
  );

  return (
    <>
      <Hero title={metadata.title} />
      <ArticleContent />
    </>
  );
}

// Powers <title>, <meta description>, and Open Graph tags
export async function generateMetadata({ params }) {
  const { metadata } = await import(`@blog/${params.slug}/markdown.mdx`);

  return {
    title: metadata.title,
    description: metadata.description,
    openGraph: {
      title: metadata.title,
      description: metadata.description,
      images: [metadata.image],
    },
  };
}

// Tells Next.js which slugs to pre-render at build time
export async function generateStaticParams() {
  const { posts } = await getSomehowListOfArticles();

  return posts.map((p) => ({ slug: p.slug }));
}

That is another reason I like MDX here: one file gives me both the content and the metadata I need for the page itself.

Final Thoughts

MDX is not the answer to every content problem, and I would not use it for every kind of product. But for a personal portfolio blog built with React and Next.js, it feels like a great fit. It gives me the simplicity of Markdown, the flexibility of React, and a workflow that stays very close to the codebase. If you are building a developer-focused site, documentation section, or personal blog and you want your content to live close to your code, MDX is absolutely worth trying.

Sometimes the best tooling choice is not the most powerful one. It is the one that lets you keep writing.

MDXNext.jsReactBlogging