Nime

Adding math support to a Gatsby MDX blog

I wanted to add support to display equations on my blog. A popular tool to achieve this is KaTeX.

The goal is being able to write a string in the TeX syntax inside a .mdx file, and a pretty math equation showing up on the rendered page.

My blog uses gatsby-plugin-mdx to be able to use .mdx files.

To achieve the goal of writing KaTeX blocks in MDX and getting pretty equations to show up, several things need to happen.

gatsby-plugin-mdx allows you to pass plugins it will use internally.
We’ll use that to “make pretty math happen” 💅!

Pinpoint the math sections in mdx

The first step is getting the MDX toolchain to recognize the math inside a .mdx file.

I used the remark-math plugin for this. It takes your markdown, looks for the relevant math sections, and marks them as such.

An inline math section is opened with a single dollar sign, and ended by an other single dollar sign $.
A math section that should be a block (so: it should start on a new line) is opened and closed by double dollar signs $$.

index.mdx
Some math on the same line: $T_n = a + (n-1)d$
Or, some math in a block
$$
T_n = a + (n-1)d
$$

The way markdown works has to do with Abstract syntax trees. They’re terribly complex at times, but they’re also very fascinating and extremely powerful tools.

remark-math manipulates that tree in such a way that your inline math turns into an inlineMath node, and your block math turns into a math node.

At the end of the MDX pipeline, HTML is rendered.

  • The inline math blocks will turn into spans with a class of math-inline.
  • A math block will turn into a div with a class of math-display.

The content of those HTML-elements will be a string with the TeX you wrote in the .mdx file.

So $T_n = a + (n-1)d$ turns into <span class="math math-inline">T_n = a + (n-1)d</span>

While the block

index.mdx
$$
T_n = a + (n-1)d
$$

Turns into <div class="math math-display">T_n = a + (n-1)d</div>

Wiring up remark-math

Install the remark-math plugin, and add it to the configuration option of gatsby-plugin-mdx in gatsby-config.js.

gatsby-config.js
module.exports = {
// --- snip ---
plugins: [
{
resolve: "gatsby-plugin-mdx",
options: {
remarkPlugins: [require("remark-math")],
},
},
// --- snip ---
],
};

Doing something with that information

There are several ways to continue from here. Wichever way you choose, adding the required KaTeX CSS file whenever math is rendered is crucial.

Option 1: a Rehype plugin

While a remark plugin like remark-math does work on the AST of the mdx before it turned into HTML.

A rehype plugin does work after that HTML is generated and changes it.

You can use rehype-katex for this.

It takes in those <span class="math-inline"> and <div class="math-display"> and turns the string of KaTeX you wrote into something that’s ready to be rendered into a beautiful equation. It just needs some of that CSS.

Wiring up rehype-katex

Install the rehype-katex plugin, and add it to the configuration option of gatsby-plugin-mdx inside the gatsby-config.js file.

Terminal window
npm i rehype-katex
gatsby-config.js
module.exports = {
// --- snip ---
plugins: [
{
resolve: "gatsby-plugin-mdx",
options: {
remarkPlugins: [require("remark-math")],
rehypePlugins: [require("rehype-katex")],
},
},
// --- snip ---
],
};

If you chose option 1, you’re done! Pretty math, yay!

Option 2: a React component

I like the React workflow, so this is the option I chose.

It’s a bit more flexible, Should I ever want to use it outside of mdx, I can.

I take in those <span class="math-inline"> and <div class="math-display"> elements, and give the string they contain to a React component to render correctly.

The React package I went with is @matejmazur/react-katex . It has a <TeX /> component that can turn the string of raw KaTeX into a form that’s ready to turn into a pretty equation on your screen.

Using react-katex

MDX allows you to intercept the HTML that gets rendered and add to, or replace it.

This is the mechanism I used to intercept those <span class="math-inline"> and <div class="math-display"> tags, and give the contents to the <TeX /> component.

The <MdxProvider /> component from @mdx-js/react allows this. If you already have one of those in your app that wraps the .mdx files you want to write math in, great!

If not, you can add one in Gatsby’s wrapRootElement in both gatsby-browser.js and gatsby-ssr.js. Or, in the layout component the .mdx file will use.

Yes, I know, there are a lot of moving parts here.

MdxLayout.js
import React from "react";
import TeX from "@matejmazur/react-katex";
import { MDXProvider } from "@mdx-js/react";
const components = {
div: (props) => {
if (props.className.includes("math-display")) {
import("katex/dist/katex.min.css");
return <TeX block math={props.children} />;
}
return <div {...props} />;
},
span: (props) => {
if (props.className.includes("math-inline")) {
import("katex/dist/katex.min.css");
return <TeX math={props.children} />;
}
return <span {...props} />;
},
};
export default function MdXLayout(props) {
return <MDXProvider components={components}>{props.children}</MDXProvider>;
}

Demo

For this blog, I chose the option that uses a React component.

I created a specialized <MathBlock /> component that handles math blocks, and gives it some extra capabilities you can specify inside mdx, by adding options next to the opening $$.

Inline math expressions are sent straight to the <TeX /> component.

aa = first item
ll = last item
nn = amount of items
dd = common difference

Input:

index.mdx
Some inline math, coming right up. $T_n = a + (n-1)d$

Output:

Some inline math, coming right up. Tn=a+(n1)dT_n = a + (n-1)d

Using a MathBlock explicitly

The MathBlock component can be used in two ways, either by passing a KaTeX string to the math prop, or by including the KaTeX string in between the opening and closing tags.

Via the math prop.

Input:

index.mdx
import { MathBlock } from "./../src/components/MathBlock";
<MathBlock title="Arithmetic progression sum" math="S_n = \frac{n(a + l)}{2}" />

Output:

Arithmetic progression sum

Sn=n(a+l)2S_n = \frac{n(a + l)}{2}

Via children.

Input:

index.mdx
import { MathBlock } from "./../src/components/MathBlock";
<MathBlock title="Arithmetic progression sum">
{"S_n = \\frac{n(2a + (n-1)d)}{2}"}
</MathBlock>

Using $$ signs in mdx

A vanilla block

Input:

index.mdx
$$
S_n = \frac{n(2a + (n-1)d)}{2}
$$

Output:

Sn=n(2a+(n1)d)2S_n = \frac{n(2a + (n-1)d)}{2}

With a meta string

Input:

index.mdx
$$ title=Arithmetic-progression-sum
S_n = \frac{n(a + l)}{2}
\newline
S_n = \frac{n(2a + (n-1)d)}{2}
$$

Output:

Sn=n(a+l)2Sn=n(2a+(n1)d)2S_n = \frac{n(a + l)}{2} \newline S_n = \frac{n(2a + (n-1)d)}{2}