Skip to content

Blog posts with dynamic content using MDX

August 26, 2022

One of my favorite sites on the internet is Red Blog Games by Amit Patel. It contains a ton of visual explanations of math and algorithms, often related to computer games. Best part is that it is interactive!

I have been using Hugo to generate my blog posts, for a few years now. The posts are written in Markdown and then automatically turned into HTML. This has worked really well, but is not great for interactive content. A few weeks back, someone introduced me to MDX! This post covers some of what I have learnt.

What is MDX?

MDX is a way to use JSX component (used by React, SolidJS) inside your Markdown. This means that we can combine use the power of Markdown (ease of writing and formatting texts) with the power of javascript (ease of writing and formatting code), where it is needed.

Why MDX?

Before we go deeper, why would you want to use MDX rather than plain Markdown?

Installing MDX

MDX is supported by many of the modern JS runtimes. The MDX website has a good quick start for most of them. Here is a selection:

In my case, I use Astro to generate this site. Enabling MDX support is as easy as running:

npm run astro add mdx

Then we can just create a new file called src/pages/post.mdx.

MDX Syntax

Now that our MDX setup is working, let’s look at the syntax. I think the easiest way to understand it is to look at the examples.

Input:

// We can define variables...
export const frameworkName = 'MDX';

// And use them as so.
<h3>Hello, {frameworkName}!</h3>

// Regular Markdown works as well
This is a paragraph of regular Markdown with **bold**,
__italic__ and ~~strikethrough~~. This works as expected.

// We can import components from other files
import Example from '../components/Example.tsx';

<Example initialValue={2} />

Output:

Hello, MDX!

This is a paragraph of regular Markdown with bold, italic and strikethrough. This works as expected.

That’s pretty neat!

MDX Gotchas

Before we continue, here are a few gotchas that I ran into.

1) When importing a component you need an extra line break after them. More info here: Stack Overflow. I think this generally applies when mixing javascript statements and MDX elements.

// ❌ Not good
import Example from '../components/Example.tsx';

<Example initialValue={2} />

// ✅ Good!
import Example from '../components/Example.tsx';

<Example initialValue={2} />

2) Variables need to be exported, otherwise they are not accessible to the templates.

// ❌ --> "frameworkName is not defined"
const frameworkName = 'MDX';
<h3>Hello, {frameworkName}!</h3>

// ✅ Good!
export const frameworkName = 'MDX';
<h3>Hello, {frameworkName}!</h3>

3) I am using MDX inside the static site generator Astro for this blog. By default it doesn’t ship any javascript to the frontend, meaning the counter will not work. We can add the client directive client:load to fix that. More about it here: astro.build#client-directives.

<Example initialValue={2} client:load />

More MDX examples!

Let’s look at some more examples. Since this is not a tutorial on React or Tailwind CSS (which I use for layout), I focus on how the code looks in the MDX file.

Example 1 - Interactive Chart

Component code is available here.

// Import component
import {Chart} from './components/Chart';

// Pass props
<Chart initialYear={2022} />

Lines of code written in year

Semi-accurate estimation of number of lines of code that I have written in a given year. Inspired by reality and then weighted by fair dice roll.

J

F

M

A

M

J

J

A

S

O

N

D

Example 2 - User poll

Component code is available here.

// Import component
import {Poll} from './components/Poll';

// Pass props
<Poll question="What is MDX?"/>
What is MDX?

Conclusion

Working with MDX is pretty easy, and fun. It can make your content interactive and potentially more understandable. It has good support with other modern web technologies like Next.js, Astro and Vite. Two potential downsides are

Overall MDX looks very promising, and I hope to get to use it more in the future. Hope you enjoyed this post.

Please send any feedback or comments to @wahlstra.