DEV Community

Cover image for MDX 2 with Next.js and Embed Components (ESM version)
Patrick for Zentered

Posted on • Originally published at zentered.co

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

Motivation and Introduction

We've crafted quite a few websites now with Next.js and we're really loving the experience and outcomes. Thanks to Opstrace we were able to Open Source a huge component to render product documentation on your own Next.js page: 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.

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/remark/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 Twitter, 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 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.

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:

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

Bonus: it works with ESM.

Getting Started

You can either start from scratch or upgrade your existing Next.js/MDX project. As a reference, we created a repository on GitHub 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. You'll need to add the pageExtensions and webpack config. Feel free to add / remove remark/rehype plugins here.

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.

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.

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

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

This is a demo post.

### Twitter Embed

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

### 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:

The Markdown for this is very simple:

### 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.

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 or let us know on Twitter. We'd love to hear from you.

Top comments (0)