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 $$
.
1Some math on the same line: $T_n = a + (n-1)d$23Or, some math in a block45$$6T_n = a + (n-1)d7$$
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
span
s with a class ofmath-inline
. - A math block will turn into a
div
with a class ofmath-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
1$$2T_n = a + (n-1)d3$$
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
.
1module.exports = {2 // --- snip ---3 plugins: [4 {5 resolve: "gatsby-plugin-mdx",6 options: {7 remarkPlugins: [require("remark-math")],8 },9 },10 // --- snip ---11 ],12};
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.
npm i rehype-katex
1module.exports = {2 // --- snip ---3 plugins: [4 {5 resolve: "gatsby-plugin-mdx",6 options: {7 remarkPlugins: [require("remark-math")],8 rehypePlugins: [require("rehype-katex")],9 },10 },11 // --- snip ---12 ],13};
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.
1import React from "react";2import TeX from "@matejmazur/react-katex";3import { MDXProvider } from "@mdx-js/react";45const components = {6 div: (props) => {7 if (props.className.includes("math-display")) {8 import("katex/dist/katex.min.css");9 return <TeX block math={props.children} />;10 }11 return <div {...props} />;12 },13 span: (props) => {14 if (props.className.includes("math-inline")) {15 import("katex/dist/katex.min.css");16 return <TeX math={props.children} />;17 }18 return <span {...props} />;19 },20};2122export default function MdXLayout(props) {23 return <MDXProvider components={components}>{props.children}</MDXProvider>;24}
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.
= first item
= last item
= amount of items
= common difference
Input:
1Some inline math, coming right up. $T_n = a + (n-1)d$
Output:
Some inline math, coming right up.
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:
1import { MathBlock } from "./../src/components/MathBlock";2<MathBlock title="Arithmetic progression sum" math="S_n = \frac{n(a + l)}{2}" />
Output:
Via children
.
Input:
1import { MathBlock } from "./../src/components/MathBlock";2<MathBlock title="Arithmetic progression sum">3{"S_n = \\frac{n(2a + (n-1)d)}{2}"}4</MathBlock>
Output:
Using $$
signs in mdx
A vanilla block
Input:
1$$2S_n = \frac{n(2a + (n-1)d)}{2}3$$
Output:
With a meta string
Input:
1$$ title=Arithmetic-progression-sum2S_n = \frac{n(a + l)}{2}3\newline4S_n = \frac{n(2a + (n-1)d)}{2}5$$
Output: