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}
into6.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
:
Stumbled upon a @nextjs / @reactjs / @mdx_js feature I've missed from @GoHugoIO for quite some time:
β Patrick Heneise (@PatrickHeneise) March 28, 2022
Embeds!https://t.co/Mh3vjkfemt
Putting together a little something for next/mdx2, stay tuned.
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.defaultconst oembedTransformer = fauxOembedTransformer.defaultconst 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 mdxconst Post = dynamic(import(`content/posts/${post}.mdx`))// dynamic import because not ESM compatibleconst embeds = dynamic(() => import('mdx-embed'))const { CodePen, Gist } = embedsconst 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 worlddate: '2022-04-01'preview: showing off twitter embeds---import { CodePen, Gist } from 'mdx-embed'This is a demo post.### Twitter Embedhttps://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="Thisis 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.