# MDX 2 with Next.js and Embed Components (ESM version)

Markdown for the component era.

- **Author:** Patrick Heneise
- **Published:** 2022-04-01
- **Updated:** 2022-04-01
- **Tags:** nextjs, mdx, mdx2

---

import Image from '../../src/components/Image.astro'

## Motivation and Introduction

We've crafted quite a few websites now with [Next.js](https://nextjs.org/) and
we're really loving the experience and outcomes. Thanks to
[Opstrace](https://opstrace.com/) we were able to Open Source a huge component
to render product documentation on your own Next.js page:
[Next Product Docs](https://github.com/opstrace/next-product-docs).

The entire Markdown ecosystem is evolving though and we're happy to welcome the
release of MDX 2 which brings a set of really cool new features, such as:

- 📝 Improved syntax makes it easier to use markdown in JSX
- 🧑‍💻 JavaScript expressions turn `{2 \* Math.PI}` into `6.283185307179586`
- 🔌 New esbuild, Rollup, and Node.js integrations
- and much more. You can [read the announcement here](https://mdxjs.com).

As it is in an ecosystem, some parts are moving faster or slower than others, so
moving to MDX 2 isn't without a few hoops to jump.

We're going to integrate
[unified](https://unifiedjs.com)/[remark](https://github.com/remarkjs/remark)/[rehype](https://github.com/rehypejs/rehype)
for improved Markdown handling and extensibility with useful features such as
GitHub Flavored Markdown, external link handling etc. and we're also integrating
embeds to show off your work on 3rd party platforms such as Instagram etc.
rehype is a tool that transforms HTML with plugins, while remark transforms
Markdown.

In this article we're using two different kind of embed mechanisms:

- [remark-embedder](https://github.com/remark-embedder)
- [mdx-embed](https://www.mdx-embed.com)

`remark-embedder` is awesome because it converts simple links into embeds based
on the oEmbed format. Everything is done during build/render time, so the embed
code is rendered directly into the compiled `html` without any client-side
JavaScript. You can find a
[list of supported oEmbed providers here](https://oembed.com/providers.json).

`mdx-embed` takes a different approach and offers components that can be
imported in `mdx`. They have a different set of components/embeds.

The good news: you can use both approaches in the same file if you wish to.
Here's an example rendered with `remark-embedder`:

**Bonus:** it works with **ESM**.

<Image src="/images/articles/2022-04/mdx-blog.png" width={1392} height={2247} />

## Getting Started

You can either [start from scratch](https://nextjs.org/docs) or upgrade your
existing Next.js/MDX project. As a reference, we
[created a repository on GitHub](https://github.com/zentered/nextjs-mdx2) with a
working example.

### Setup

We're listing the required packages, _use your preferred package manager to
install those_.

#### The basics (MDX loader for Next.js):

```
    @mdx-js/loader @next/mdx
```

#### remark/rehype plugins

```
    rehype-external-links remark-gfm remark-frontmatter
```

- List of remark plugins:
  https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins
- List of rehype plugins:
  https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins

#### remark-embedder

If you want to include oEmbeds during build time:

```
    @remark-embedder/core @remark-embedder/transformer-oembed
```

#### mdx-embed

And last but not least "mdx-embed" for embedding components:

```
    mdx-embed
```

#### Next.js Config

We're integrating mdx handling via webpack and the
[@mdx-js/loader](https://mdxjs.com/packages/loader/). You'll need to add the
`pageExtensions` and webpack config. Feel free to add / remove remark/rehype
plugins here.

```js
import remarkFrontmatter from 'remark-frontmatter'
import remarkGfm from 'remark-gfm'
import rehypeExternalLinks from 'rehype-external-links'

import fauxRemarkEmbedder from '@remark-embedder/core'
import fauxOembedTransformer from '@remark-embedder/transformer-oembed'
const remarkEmbedder = fauxRemarkEmbedder.default
const oembedTransformer = fauxOembedTransformer.default

const nextConfig = {
  reactStrictMode: true,
  experimental: { esmExternals: true },
  pageExtensions: ['md', 'mdx', 'jsx', 'js'],
  webpack: function (config) {
    config.module.rules.push({
      test: /\.mdx?$/,
      use: [
        {
          loader: '@mdx-js/loader',
          options: {
            rehypePlugins: [rehypeExternalLinks],
            remarkPlugins: [
              remarkGfm,
              remarkFrontmatter,
              [remarkEmbedder, { transformers: [oembedTransformer] }]
            ]
          }
        }
      ]
    })
    return config
  }
}

export default nextConfig
```

#### JSX Page

You can create a new page (ie `[post].jsx`). We're able to use a dynamic import
to load the contents of the MDX file as a regular component. We're also
dynamically importing `mdx-embed`, as the module is not ESM compatible yet.

```jsx
import dynamic from 'next/dynamic'
import { getSlugs, getMeta } from 'utils/posts.js'
import { MDXProvider } from '@mdx-js/react'

export default function Post({ post, meta }) {
  // import mdx
  const Post = dynamic(import(`content/posts/${post}.mdx`))

  // dynamic import because not ESM compatible
  const embeds = dynamic(() => import('mdx-embed'))
  const { CodePen, Gist } = embeds

  const components = {
    CodePen,
    Gist
  }

  return (
    <div>
      <main>
        <h1>{meta.title}</h1>
        <article className="prose">
          <MDXProvider components={components}>
            <Post />
          </MDXProvider>
        </article>
      </main>
    </div>
  )
}
```

#### MDX Content

This is the file that's being rendered. It's a simple MDX file using regular
Markdown YML frontmatter, rather than an export. You can change that to your
liking. The components need to be imported again, otherwise there's an error,
you can find the full
[list of supported components on the mdx-embed page](https://www.mdx-embed.com).

```markdown
---
title: hello world
date: '2022-04-01'
preview: showing off embeds
---

import { CodePen, Gist } from 'mdx-embed'

This is a demo post.

### Embeds

### The Gist ...

<Gist gistLink="PatrickHeneise/bbca1a8c4816f92aa3796db41a4a6203" />

### And the Pen

<CodePen codePenId="PNaGbb" />
```

In addition to those embed components, you can also add your own JSX components.
As an exmaple, we created a small `<Gallery>` component taking an array of
images and displaying that in a grid:

<Image
  src="/images/articles/2022-04/mdx-gallery.png"
  width={2032}
  height={1180}
/>

The Markdown for this is very simple:

```markdown
### Component

<Gallery images={['cyprus1.jpeg', 'cyprus2.jpeg', 'cyprus3.jpeg']} caption="This
is a caption" />
```

This opens up a whole new world of possibilities when it comes to Markdown
content.

## Summary & Feedback

In this article we explored the basics of and upgrade to MDX2. We also learned
how to add rehype/remark plugins to add features to our Markdown content during
render/build time and how to use the mdx-embeds to create an easy and rich
experience with 3rd party platforms. You can find the
[minimal example blog on GitHub](https://github.com/zentered/nextjs-with-mdx2).

We hope you enjoyed this article and it'll help you on your journey with Next.js
and MDX2. If you have any feedback, comments, ideas about this article, please
[share them here](https://github.com/zentered/feedback/discussions/2) or let us
know on [Bluesky](https://bsky.app/profile/zentered.co). We'd love to hear from
you.
