Recipe: MDX Blog

Picture of MariusMarius Espejo

Wanted to jot down a few quick scrappy notes on how to put together a basic MDX blog like this one.

But first, why MDX?

I first got the idea of creating written content using markdown when I was exploring platforms for publishing written developer content and discovered that DEV has this really nice Markdown-first editor.

However why do you need another editor or UI when you can just use your own code editor?!

Markdown uses a very simple syntax that allows you to very quickly structure your content without needing to play around with a GUI for visual formatting. If you're a developer and have written at least even just a basic README you probably already know what I'm talking about.

Now, take Markdown and add React/JSX into the mix and what you get is MDX. Basically it allows you to start simple with some nice and basic structured content, but then when you want to add custom components into the mix, you can!


MDX can be transformed into HTML. For example the heading of this section could be transformed from

### MDX to HTML


<h3>MDX to HTML</h3>

Also notice that this post has some nice syntax highlighting. MDX allows you hook into that transformation for greater customization. More on that later!

Once you have HTML... well you can pretty much deploy or host that anywhere. Recipe complete!

Ok, but what tools do you actually need to use?

I suggest picking any server framework that either has built-in support for Markdown/MDX OR utilize something like mdx-bunlder to do the bundling/rendering for you in pretty much any framework.

Here are a few suggestions:

There are honestly a ton of other options, pretty much any modern SSG/SSR framework now has some kind of support for MDX. And if you use mdx-bundler you can pretty much use that with anything.

Which one am I using?

TL;DR NextJS with mdx-bundler.

I personally originally started with Remix however I really wanted to utilize mdx-bundler at build time. Now with that said, mdx-bunlder is framework agnostic, however Remix promotes an SSR-only approach. Meaning anything that would take time to build at runtime likely should utilize some kind of caching. For example, Kent C. Dodd's blog runs on Remix and he caches the compilation in Redis and has a fairly involved workflow just to make sure things are properly cached and invalidated when needed.

I personally wanted to keep things simple (with minimal infrastrucure) and wanted the option to use SSG, something that NextJS allows. This does come at a cost of needing to re-build every time I publish a new post or make a small typo change. But that's a reasonable tradeoff for me... for now.

Styling the generated HTML

You can of course decide to write your own CSS to style the generated HTML. Probably something I'll do in the future myself, however for now I decided to use @tailwindcss/typography which allows you a very quick and beautiful default.

Installing tailwind for a NextJS app is fairly well documented. So getting that all setup is pretty easy.

You might also want syntax highlighting

There are pretty much two main ways to do it (via rehype plugins). Either use highlight.js or prism. You can't really go wrong with either one.

I personally went with prism because rehype-prism-plus allows me to add additional features like line numbers and line highlighting. For example:

function helloWorld(arg) {
  console.log(`hello I'm highlighted!`);

Whichever MDX solution you decide to use, there is usually a way to pass in custom remark/rehype plugings, for example here's how to do it with mdx-bundler.

import rehypePrismPlus from "rehype-prism-plus";
import someRemarkPlugin from "...";
  source: mdxSource,
  mdxOptions(options, frontmatter) {
    options.remarkPlugins = [
      ...(options.remarkPlugins ?? []),
    options.rehypePlugins = [...(options.rehypePlugins ?? []), rehypePrismPlus];
    return options;

That's pretty much it!

Put those things together and you've got yourself a fairly simple MDX blog which you can deploy almost anywhere!

I'll write about this in more detail with my specific setup with Next at another time, but for now hopefully this is helpful to some.

👈 More posts