Last updated on October 07, 2021

Setup MDX Bundler with Next.js

A couple of months ago, I decided to finally build my own web development blog where I could share all of my knowledge, struggles, and solutions to complex problems. Until then, I had been using Meteor in all of my projects for almost 7 years and even though it is one of the most amazing frameworks I have ever used before, we as developers need to learn how to choose the right tools for the project's needs.

Now, for this project, I was looking for a tool that could use markdown, syntax highlighting, and server-side rendering right out of the box. After doing some digging around the web, I finally chose to use the framework Next.js.

Next.js has some great documentation and learning tutorials; you can even find a complete repository full of code examples which cover a broad amount of topics and integrations that will help you get started very quickly. Now my first approach was to use the blog-starter example from this repository and after a couple of changes, I had a production-ready version of my blog.

For a while, I had been using a simple markdown integration but the more that I worked on my project, the more I started to need custom react components in my posts. Because of this, I decided to implement MDX.

When I started to investigate how to implement MDX in Next.js, I found multiple packages that could be used for this functionality which are:

Each one of these packages has its advantages and drawbacks but after some careful investigation, I chose to use the mdx-bundler package because it is framework agnostic which is a great feature to have if you ever intend to move your platform to another technology, it supports frontmatter and it also supports importing react components from the MDX content.

Now that we have chosen our MDX package, we will start by setting it up in our project. To make this tutorial easy to follow through, I made a very simple markdown-boilerplate which is a basic Next.js blog with a markdown integration.

MDX Bundler Setup

The first thing that we will do is install the mdx-bundler package with the command npm add --save mdx-bundler. Now to make sure this package works correctly, we also need to install a devDependency into our project called esbuild. For this, we will run the command npm install --save-dev esbuild.

Once we have the packages installed, we can start configuring MDX in our project. We will create a new file inside of the lib folder called mdx.js which will contain a simple function that will receive our MDX content as a string and it will compile it into html. Let's copy into this file the following code:

lib/mdx.js
import { bundleMDX } from "mdx-bundler";
 
export async function mdxToHtml(content) {
  const { code } = await bundleMDX({
    source: content
  });
  return code;
}

Now, let's use this mdxToHtml() function to convert our MDX content into html. For this, we will open the file pages/posts/[slug].js which is the file that renders the dynamic routes of our blog posts. Here, we will update our Post component and our getStaticProps function to look like this:

pages/posts/[slug].js
import { useMemo } from "react";
import { getMDXComponent } from "mdx-bundler/client";
import { mdxToHtml } from 'lib/mdx';
 
const Post = ({ meta, content }) => {
  const Component = useMemo(() => getMDXComponent(content), [content]);
  return <Component />;
};
 
export async function getStaticProps({ params }) {
  const post = await getPostBySlug(params.slug);
  const content = await mdxToHtml(post.content);
 
  return {
    props: { ...post, content }
  }
}

The getStaticProps function will retrieve from the server all the information of the blog post that we currently want to display. This information contains values like our post slug, metadata, and MDX content. As soon as we have this information, we will execute our mdxToHtml() function which will then compile the MDX content into html. Once it is set, we will return these values which are then sent to the Post component as props.

Before we complete our basic MDX setup, we need to update our getPostBySlug function so it can read MDX files. For this, we will open the file lib/posts.js and inside of it we will update the function getPostBySlug to look like this:

export function getPostBySlug(slug) {
  const realSlug = slug.replace(/\.mdx/, "");
  const fullPath = join(postsDirectory, `${realSlug}.mdx`);
  const fileContents = fs.readFileSync(fullPath, "utf8");
  const { data, content } = matter(fileContents);
 
  return { slug: realSlug, meta: data, content };
}

As you can see here, we changed the format of both the realSlug and fullPath variables to read .mdx files instead of .md files.

Now, we have completed the basic structure of our MDX functionality. To finish, we will add one of the most used and valuable features which is syntax highlighting. For this, we will use remark-prism, and to set it up, we will start by installing the package with the command npm install --save remark-prism. Once the installation is complete, we will go back into the lib/mdx.js file and we will update its content with the following code:

lib/mdx.js
import { bundleMDX } from "mdx-bundler";
import remarkPrism from "remark-prism";
 
export async function mdxToHtml(content) {
  const { code } = await bundleMDX({
    source: content,
    mdxOptions: (options) => {
      options.remarkPlugins = [remarkPrism]
      return options;
    },
  });
 
  return code;
}

Conclusion

Congratulations, we now have a basic MDX setup in our Next.js project. I hope this tutorial helped you understand more about MDX, the different packages that could be used with Next.js, and the reason why mdx-bundler could be the best option for you.

If you want to view the code, I created a simple mdx-bundler-boilerplate which contains all the changes we went through in this tutorial.