Markdown to MDX: How to Convert Your Blog

Arrow pointing from Markdown logo to MDX log

You are here because you want to convert your blog over from Markdown to MDX, you are in the right place.

Originally, I created this blog with create-next-app following this tutorial provided by Vercel, so my learning is from converting a Next.js app. The details may be a little different for your site (Hugo, Gatsby, React, Angular, etc), but I keep things at a high level so anyone can learn here.

The quick version of moving from Markdown to MDX.

  1. Convert Markdown files to MDX files: To do this, simply add an 'X' to the end of all your markdown filenames, ⇒ blogPost.mdx.
    IMPORTANT NOTE: HTML tags in Markdown text need to be turned into JSX string literal, see below
  2. Change rendering package to one that supports MDX: I switched to [next-mdx-remote]( for my MDX content.
  3. Important Note: If you have any HTML tags in your Markdown, for example I had text like: "In the <body> tag above you find....". The <body> tag breaks MDX. You need to change them into string literals in the JSX {'<body>'}

Continue reading to learn more and see why I made certain design decisions.

1. Converting Markdown files to MDX

This really is the simple part. As long as your Markdown does not have any HTML tags in it, then adding an X to the end of the file type should be all you need. → blogPost.mdx

Any frontmatter can stay, MDX supports that as well. It's not required to change all files over to MDX, especially if you don't plan on taking advantage of MDX features in old Markdown files. But I thought in the long run it would be easier for me and the logic to convert everything over to MDX instead of having a mix of the two types.

2. Rendering the MDX files to the page

Choosing a MDX Package

We need a package that will take our MDX files and turn them into beautiful renderable HTML.

There are two main options to choose:

  1. next-mdx official Vercel implementation
  2. next-mdx-remote HashiCorp implementation

I went with next-mdx-remote because I found the documentation and conversion over from my existing Markdown implementation easier.

Conversion to use next-mdx-remote

In my Next.js page, I have a single layout file that dynamically renders all blog posts. I made minimal code changes to the file to begin supporting MDX. See the changes below

New code for rendering MDX

1// pages/posts/[id].js 2import { serialize } from 'next-mdx-remote/serialize' 3import { MDXRemote } from 'next-mdx-remote' 4 5export async function getStaticProps({ params }) { 6 const postData = await getPostData(; 7 const source = await serialize(postData.markdown) 8 9 return { 10 props: { source, postData } 11 } 12} 13 14// How the MDX is now rendered 15<MDXRemote {...source} components={CodeBlock} />

Old that rendered Markdown only

1// pages/posts/[id].js 2import ReactMarkdown from "react-markdown"; 3 4export async function getStaticProps({ params }) { 5 const postData = await getPostData(; 6 return { 7 props: { 8 postData, 9 }, 10 }; 11} 12 13// How I rendered my Markdown 14<ReactMarkdown components={CodeBlock}>{postData.markdown}</ReactMarkdown>

It may be hard to spot the differences at first glace because that's how it was, let me explain.

The getPostData() method that you see in both the new and old basically did not change. A single character changed it in to say look for mdx files instead of md files. That's it. So I was already pulling in the data I needed and this conversion over could not have been simpler.

But the addition in the new is the serialize method that I passed my MDX content into, passing the source data into my props, and the new MDXRemote component that consumes the source and is what displays my beautiful blog, such as this one!

A few other minor changes

I make a few other minor changes, but these are more specific to my blog implementation and may not concern you.

  • /lib file changes: I had to make a few changes to the service methods I have under my /lib folder. These were simple changes like telling the method that finds all posts to now look for files ending in .mdx instead of .md. I also changed the method to strip of the .mdx extension from the file name when creating URL's.
  • Removing old package: When I was a Markdown only blog I used React-Markdown to render it to HTML. I removed that package from package.json to keep things clean.

Ending Thoughts

You now have a blog that supports MDX! Now it's time to renovate your app and add in the JSX goodness. Share with me on Twitter @ThomasJDesmond what you build!

Construction site renovating a home