# How to MDX with Gatsby

An introduction to MDX, and how to configure and set it up on an existing Gatsby
site.

## MDX explained briefly

MDX is like Markdown, **but it also renders React components**: if you type in
`<div>foo<div>`, MDX will render a component. This means that you need to close
all your elements, or MDX will crash, it works just like React.

In MDX there are two keywords that have meaning: `import` and `export`. Other
than that (and components), anything you write in an MDX document will be
printed as text. For example `const foo = ['baz', 'quix']` has no meaning in MDX
and will just printed out as a string.

## Installing MDX to Gatsby project

Install the needed packages:

```shell
$ npm install --save gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react
```

## Configuring MDX

[`gatsby-plugin-mdx`](https://www.gatsbyjs.com/plugins/gatsby-plugin-mdx/) will
be replacing
[`gatsby-transformer-remark`](https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/)
entirely. In my case it included the following changes to the `gatsby-config.js`
file:

```diff
{
- resolve: 'gatsby-transformer-remark',
+ resolve: 'gatsby-plugin-mdx',
  options: {
+   extensions: ['.mdx', '.md'],
-   excerpt_separator: '{/* more */}',
-   plugins: [
+   gatsbyRemarkPlugins: [
      'gatsby-remark-responsive-iframe',
      {
        resolve: 'gatsby-remark-autolink-headers',
        options: {
          offsetY: 0
        }
      },
      {
        resolve: 'gatsby-remark-prismjs',
        options: {
          classPrefix: 'language-',
          inlineCodeMarker: null
        }
      }
    ]
  }
}
```

I’m using
[`gatsby-remark-autolink-headers`](https://www.gatsbyjs.com/plugins/gatsby-remark-autolink-headers/)
and
[`gatsby-remark-prismjs`](https://www.gatsbyjs.com/plugins/gatsby-remark-prismjs/)
plugins, for you that section might look different.

Unfortunately, at the time of writing this, `gatsby-plugin-mdx` has no way of
handling excerpt separator: `excerpt_separator: '{/* more */}'`. I found an
[discussion in the Gatsby GitHub](https://github.com/gatsbyjs/gatsby/issues/16865),
but not much else.

`gatsby-transformer-remark` can be now safely uninstalled:

```shell
$ npm uninstall gatsby-transformer-remark gatsby-plugin-feed
```

## Update templates and queries

Since `allMarkdownRemark` is now gone, all references to it has to be updated,
because `gatsby-plugin-mdx` uses different naming internally. Your code editor’s
search and replace abilities come handy here, I simply searched and replaced
these strings using VSCode:

- Replace `allMarkdownRemark` with `allMdx`
- Replace `'MarkdownRemark'` with `'Mdx'`
- Replace `markdownRemark` with `mdx`
- Replace `rawMarkdownBody` with `rawBody` (if you’re using this)

**Remember to do case sensitive matching**. I just blasted those through pretty
carelessly and I didn’t break anything, but check that your replacement isn’t
too greedy etc.

## Setup MDX rendering

Import the `MDXRenderer` in the file where you’re rendering the blog posts:

```js
import { MDXRenderer } from 'gatsby-plugin-mdx'
```

Then, somewhere in your GraphQL query you’ve got the `post.html` that holds the
contents for a post, replace that with `post.body`, and pass it into the
`MDXRenderer` React component:

```diff
- <div dangerouslySetInnerHTML={{ __html: post.html }} />
+ <MDXRenderer>{post.body}</MDXRenderer>
```

That’s pretty much it, restart the dev server and see if it works. As a bonus,
getting rid of `dangerouslySetInnerHTML` feels right.

For me that was just the beginning, I needed to sort out a ton of old posts that
didn’t work properly with MDX.

## Fixing rendering problems with legacy posts

I had few hundred aging entries to deal with, originally imported from
WordPress, and some of them had HTML elements that weren’t closed properly, or
CSS written inside `<style>` tags that made the build process crash, giving me
an absolute deluge or errors which was hard to make sene of.

So I removed the line `extensions: ['.mdx', '.md]` from the `gatsby-config`
(`gatsby-plugin-mdx` defaults to `.mdx` file extension), then changed the
extensions one-by-one to `.mdx` on all of the posts, this way I could see
exactly which post had an error.

## Importing components

The importing works just as you’d expect, here’s an example `.mdx` file:

```
---
title: My cool post
date: 2020-11-24 23:00
---

import { PullQuote } from '../src/components/PullQuote'

## Interesting title

Lorem ipsum dolor...

<PullQuote source="Something interesting" />
```

If the `PullQuote` component is used a lot, it can be **included as a kind of a
global**, also referred to as **shortcodes**. This can be done with the
`MDXProvider`.

Make the sure the `MDXProvider` wraps the component that renders the blog post,
in my case it’s a component called `Post` inside my `BlogPost` template:

```jsx
import React from 'react'
import { Link } from 'gatsby'
import { YouTube } from '../../components/Embeds'
import PullQuote from '../../components/PullQuote'

const shortCodes = { Link, YouTube, PullQuote }

const BlogPost = props => (
  <Base>
    <PostWrapper>
      <MDXProvider components={shortCodes}>
        <Post post={props.data.mdx} />
      </MDXProvider>
      <Sidebar />
    </PostWrapper>
  </Base>
)
```

## Defining variables

You can’t do `const foo = 'bar'` that would be printed as is. Only two native
JavaScript keywords can be used: `import` and `export`. So you can do:

```js
export const listOfThings = ['chair', 'plank', 'dog', 'pea']
```

Then use your variable anywhere in the post.

## How to run Markdown inside HTML components

Maybe you’ve got something like this:

```
<Note type="info">
  Lorem ipsum dolor. The `flexbox` is cool. [Read about it](/box-flex).
</Note>
```

The Markdown inside that component is not executed, it doesn’t matter if it’s a
custom React component or a normal HTML element.

**To make the Markdown parsing work inside an MDX component**, simply add line
breaks inside the element:

```
<Note type="info">

Lorem ipsum dolor ym sit. The `flexbox` is cool. [Read about it](/box-flex).

</Note>
```

## How to write CSS into a style tag

This is not very React-like, but maybe you’ve got some legacy posts that have
style tags in them etc. (I had a bunch of those).

If you write CSS into a style tag normally, it crashes the parser:

```
<style>
  .some-class {
    color: red;
  }
</style>
```

Here’s how you can fix that, wrap your CSS to a template string

``:

```
<style>{`
  .some-class {
    color: red;
  }
`}</style>
```

You can also put the CSS to another file and import it:

```js
import './my-post-styles.css'
```

Speaking of importing post specific assets.

## Keep post specific assets in the post’s directory

Not specific to MDX but somehow I’ve been using more files with MDX than before.
In Gatsby, posts are files, but a post can also be a directory, the directory’s
name is picked up as the slug. This way the post dir can hold all the assets
related to it. `index.mdx` being the entry point:

```
posts/
├── how-to-mdx-in-gatsby/
│   ├── data.json
│   ├── Graph.js
│   ├── index.mdx
│   └── styles.js
├── foo-bar.mdx
├── bar-foo.mdx
.
.
```

Or if you name your file `foo-bar.mdx` instead of `index.mdx`, you can access it
from `how-to-mdx-in-gatsby/foo-bar`, neat.

## Returning functions from MDX components

You can’t really return functions from MDX components, but you can run an IIFE
(immediately invoked function expression) inside a component which is part of an
MDX file, this way you can define variables and have state and what not.

Here’s a random example that sets a div’s content:

```
---
title: Some post
---

Lorem ipsum dolor:

<div>
  {(() => {
    const [divText, setDivText] = React.useState('Hello!')
    return (
      <>
        <div
          contenteditable
          onInput={event => setDivText(event.target.innerText)}
          className="module"
        >
          Hello!
        </div>
        <div className="module__output">
          <strong>Value:</strong> {divText}
        </div>
      </>
    )
  })()}
</div>
```

If you find yourself doing this a lot, it might better to have it as an external
component.

## Pass in data as props

You can pass any props you want into the `MDXRenderer` component:

```jsx
<MDXRenderer foo='FOO BAR'>{body}</MDXRenderer>
```

Then access the props in the MDX files like it would be a React component:

```
---
title: Foo
---

Lorem ipsum <span>{props.foo}</span> dolor.
```

If you don’t want to use an HTML element, React fragment should also work
`<>{props.foo}</>`.

## Layouts

You can have different layout per post, if you so desire. This is done by
exporting component from your post, the post is then wrapped by the template
code.

```
export default ({ children }) => (
  <div>
    <h1>Custom layout</h1>
    <div>{children}</div>
  </div>
)

## An MDX doc with a custom layout

Content here...
```

Or likewise it can be imported if that’s more convenient.

```
import CustomLayout from './src/components/custom-layout'

export default CustomLayout

## An MDX doc with a custom layout

Content here...
```

## MDX v2

The v2 will be released shortly, at the time of writing this 2.0.0-rc.2 is out.
`gatsby-plugin-mdx` has not been updated yet to v2. The v2 is apparently
completely rewritten, it’s much faster, smaller, and lighter.

### Improved syntax in v2

The up-and-coming v2 has some useful additions, most exciting probably being
that you can now write MarkDown inside HTML components, the following will parse
how you would expect:

```
<div>*hi*?</div>

<div># hi?</div>

<main>
  <div>

    # hi?

  </div>
</main>
```

### JavaScript expressions in v2

The v2 lets you run JavaScript inside curly braces `{}`:

```
PI times two is {2 \* Math.PI}
```

This means, if you want to type out the left curly brace, you need to escape it
`\{` or use the expression `{'{'}`. Same goes for the less than symbol `<`, btw.

See a more thorough [roundup on the NDX site](https://mdxjs.com/blog/v2/).

## Some MDX gotchas to keep in mind

Here’s some things that you might have issues with.

### Imports are cached

If you import a module from mdx file: `import Foo from './Foo'`, then rename it:
`import Bar from './Bar'`, Gatsby will throw an error similar to this:

```
Module build failed (from ./node_modules/gatsby/dist/utils/babel-loader.js):
Error: ENOENT: no such file or directory, open '/Users/bob/gatsby-blog/contents/posts/my-post/Foo.js'
```

The error persist if you restart the dev server. This is because the Gatsby’s
caching system can’t properly handle imports to MDX files. To fix it, run
`gatsby clean` and boot up the dev server. I have the `clean` command as a
script in my `package.json` file: `npm run clean`.

### Starting lines with html elements

Not really a gotcha, but something to keep in mind: if you start a line with an
HTML element, it won’t be wrapped in `<p>`. Not sure if this is a general
Markdown things or just MDX.

```
Normal sentence lorem ipsum.

<span>This line</span> won’t be wrapped in a paragraph.
```

There are probably many other edge cases, here’s just few to mention.

## Conclusions

MDX has changed how blog posts are written, I quite like it. And the v2 is going
to be pretty amazing.
