MDX 2 with Next.js and Embed Components (ESM version)
Tags: nextjs, mdx, mdx2
Published: 2022-04-01 00:00:00

---
cover_image: 'https://zentered.co/images/articles/2022-04/mdx.png'
date: '2022-04-01T00:00:00.000Z'
created_at: '2022-04-01T00:00:00.000Z'
edited_at: '2022-04-01T00:00:00.000Z'
crossposted_at: '2022-04-04T05:51:33.782Z'
---
<h2 data-reactroot="">Motivation and Introduction</h2>
<p data-reactroot="">We&#x27;ve crafted quite a few websites now with <a href="https://nextjs.org/">Next.js</a> and
we&#x27;re really loving the experience and outcomes. Thanks to
<a href="https://opstrace.com/">Opstrace</a> we were able to Open Source a huge component
to render product documentation on your own Next.js page:
<a href="https://github.com/opstrace/next-product-docs">Next Product Docs</a>.</p>
<p data-reactroot="">The entire Markdown ecosystem is evolving though and we&#x27;re happy to welcome the
release of MDX 2 which brings a set of really cool new features, such as:</p>
<ul data-reactroot="">
<li>📝 Improved syntax makes it easier to use markdown in JSX</li>
<li>🧑‍💻 JavaScript expressions turn <code>{2 \* Math.PI}</code> into <code>6.283185307179586</code></li>
<li>🔌 New esbuild, Rollup, and Node.js integrations</li>
<li>and much more. You can <a href="https://mdxjs.com">read the announcement here</a>.</li>
</ul>
<p data-reactroot="">As it is in an ecosystem, some parts are moving faster or slower than others, so
moving to MDX 2 isn&#x27;t without a few hoops to jump.</p>
<p data-reactroot="">We&#x27;re going to integrate
<a href="https://unifiedjs.com">unified</a>/<a href="https://github.com/remarkjs/remark">remark</a>/<a href="https://github.com/rehypejs/rehype">rehype</a>
for improved Markdown handling and extensibility with useful features such as
GitHub Flavored Markdown, external link handling etc. and we&#x27;re also integrating
embeds to show off your work on 3rd party platforms such as Twitter, Instagram
etc. rehype is a tool that transforms HTML with plugins, while remark transforms
Markdown.</p>
<p data-reactroot="">In this article we&#x27;re using two different kind of embed mechanisms:</p>
<ul data-reactroot="">
<li><a href="https://github.com/remark-embedder">remark-embedder</a></li>
<li><a href="https://www.mdx-embed.com">mdx-embed</a></li>
</ul>
<p data-reactroot=""><code>remark-embedder</code> 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 <code>html</code> without any client-side
JavaScript. You can find a
<a href="https://oembed.com/providers.json">list of supported oEmbed providers here</a>.</p>
<p data-reactroot=""><code>mdx-embed</code> takes a different approach and offers components that can be
imported in <code>mdx</code>. They have a different set of components/embeds.</p>
<p data-reactroot="">The good news: you can use both approaches in the same file if you wish to.
Here&#x27;s an example rendered with <code>remark-embedder</code>:</p>
<p data-reactroot="">https://twitter.com/PatrickHeneise/status/1508503730295037954</p>
<p data-reactroot=""><strong>Bonus:</strong> it works with <strong>ESM</strong>.</p>
<img src="https://zentered.co//images/articles/2022-04/mdx-blog.png" width="1392" height="2247" data-reactroot=""/>
<h2 data-reactroot="">Getting Started</h2>
<p data-reactroot="">You can either <a href="https://nextjs.org/docs">start from scratch</a> or upgrade your
existing Next.js/MDX project. As a reference, we
<a href="https://github.com/zentered/nextjs-mdx2">created a repository on GitHub</a> with a
working example.</p>
<h3 data-reactroot="">Setup</h3>
<p data-reactroot="">We&#x27;re listing the required packages, <em>use your preferred package manager to
install those</em>.</p>
<h4 data-reactroot="">The basics (MDX loader for Next.js):</h4>
<pre data-reactroot=""><code>    @mdx-js/loader @next/mdx
</code></pre>
<h4 data-reactroot="">remark/rehype plugins</h4>
<pre data-reactroot=""><code>    rehype-external-links remark-gfm remark-frontmatter
</code></pre>
<ul data-reactroot="">
<li>List of remark plugins:
https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins</li>
<li>List of rehype plugins:
https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins</li>
</ul>
<h4 data-reactroot="">remark-embedder</h4>
<p data-reactroot="">If you want to include oEmbeds during build time:</p>
<pre data-reactroot=""><code>    @remark-embedder/core @remark-embedder/transformer-oembed
</code></pre>
<h4 data-reactroot="">mdx-embed</h4>
<p data-reactroot="">And last but not least &quot;mdx-embed&quot; for embedding components:</p>
<pre data-reactroot=""><code>    mdx-embed
</code></pre>
<h4 data-reactroot="">Next.js Config</h4>
<p data-reactroot="">We&#x27;re integrating mdx handling via webpack and the
<a href="https://mdxjs.com/packages/loader/">@mdx-js/loader</a>. You&#x27;ll need to add the
<code>pageExtensions</code> and webpack config. Feel free to add / remove remark/rehype
plugins here.</p>
<pre data-reactroot=""><code class="language-js">import remarkFrontmatter from &#x27;remark-frontmatter&#x27;
import remarkGfm from &#x27;remark-gfm&#x27;
import rehypeExternalLinks from &#x27;rehype-external-links&#x27;

import fauxRemarkEmbedder from &#x27;@remark-embedder/core&#x27;
import fauxOembedTransformer from &#x27;@remark-embedder/transformer-oembed&#x27;
const remarkEmbedder = fauxRemarkEmbedder.default
const oembedTransformer = fauxOembedTransformer.default

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

export default nextConfig
</code></pre>
<h4 data-reactroot="">JSX Page</h4>
<p data-reactroot="">You can create a new page (ie <code>[post].jsx</code>). We&#x27;re able to use a dynamic import
to load the contents of the MDX file as a regular component. We&#x27;re also
dynamically importing <code>mdx-embed</code>, as the module is not ESM compatible yet.</p>
<pre data-reactroot=""><code class="language-jsx">import dynamic from &#x27;next/dynamic&#x27;
import { getSlugs, getMeta } from &#x27;utils/posts.js&#x27;
import { MDXProvider } from &#x27;@mdx-js/react&#x27;

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(() =&gt; import(&#x27;mdx-embed&#x27;))
  const { CodePen, Gist } = embeds

  const components = {
    CodePen,
    Gist
  }

  return (
    &lt;div&gt;
      &lt;main&gt;
        &lt;h1&gt;{meta.title}&lt;/h1&gt;
        &lt;article className=&quot;prose&quot;&gt;
          &lt;MDXProvider components={components}&gt;
            &lt;Post /&gt;
          &lt;/MDXProvider&gt;
        &lt;/article&gt;
      &lt;/main&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<h4 data-reactroot="">MDX Content</h4>
<p data-reactroot="">This is the file that&#x27;s being rendered. It&#x27;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&#x27;s an error,
you can find the full
<a href="https://www.mdx-embed.com">list of supported components on the mdx-embed page</a>.</p>
<pre data-reactroot=""><code class="language-markdown">---
title: hello world
date: &#x27;2022-04-01&#x27;
preview: showing off twitter embeds
---

import { CodePen, Gist } from &#x27;mdx-embed&#x27;

This is a demo post.

### Twitter Embed

https://twitter.com/PatrickHeneise/status/1508503730295037954

### The Gist ...

&lt;Gist gistLink=&quot;PatrickHeneise/bbca1a8c4816f92aa3796db41a4a6203&quot; /&gt;

### And the Pen

&lt;CodePen codePenId=&quot;PNaGbb&quot; /&gt;
</code></pre>
<p data-reactroot="">In addition to those embed components, you can also add your own JSX components.
As an exmaple, we created a small <code>&lt;Gallery&gt;</code> component taking an array of
images and displaying that in a grid:</p>
<img src="https://zentered.co//images/articles/2022-04/mdx-gallery.png" width="2032" height="1180" data-reactroot=""/>
<p data-reactroot="">The Markdown for this is very simple:</p>
<pre data-reactroot=""><code class="language-markdown">### Component

&lt;Gallery images={[&#x27;cyprus1.jpeg&#x27;, &#x27;cyprus2.jpeg&#x27;, &#x27;cyprus3.jpeg&#x27;]} caption=&quot;This
is a caption&quot; /&gt;
</code></pre>
<p data-reactroot="">This opens up a whole new world of possibilities when it comes to Markdown
content.</p>
<h2 data-reactroot="">Summary &amp; Feedback</h2>
<p data-reactroot="">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
<a href="https://github.com/zentered/nextjs-with-mdx2">minimal example blog on GitHub</a>.</p>
<p data-reactroot="">We hope you enjoyed this article and it&#x27;ll help you on your journey with Next.js
and MDX2. If you have any feedback, comments, ideas about this article, please
<a href="https://github.com/zentered/feedback/discussions/2">share them here</a> or let us
know on <a href="https://twitter.com/zenteredco">Twitter</a>. We&#x27;d love to hear from you.</p>