Guide to Using Mdx-bundler With Next.js

Published on Nov 23, 2021
~9 min read

When I was building this blog, I knew I wanted a lot of customization (I'm a junior dev who loves customization). In order to get maximum customization I found out I could use MDX for my blog posts.

MDX is an extension on Markdown which, lets you import custom React components into your blog posts. To use MDX with Next.js you need to use a separate package. There are a few choices with MDX-remote being a popular one, but it has some drawbacks. For that reason, I chose to use mdx-bundler.

What mdx-bundler allows you to do is bundle React components into your blog posts. I use it for reusable custom components, things like image styling, the code blocks you see in my posts and, the anchor tags.

When I was setting mdx-bundler up I had just finished my Bootcamp and I didn't know what I was doing (I still don't ha). Meaning I ran into a lot of hurdles and got quite frustrated. So, now that I have it "working" I want to help others get it working on their Next.js blogs because it really is a joy to work with.

This post aims to do just that by breaking down how to use mdx-bundler with Next.js. So let's get into it, starting at step 1, installation.

Installation

Installation is pretty straight forward and to be honest you can follow the instructions at the official GitHub repo. But, for the sake of not forcing you to switch tabs let's go through it below.

BASH
npm install --save mdx-bundler esbuild
// OR
yarn add mdx-bundler esbuild

Yep, it is that simple. Now with that out of the way, it's time to unleash the power of mdx-bundler on your Next.js project.

Adding Mdx-Bundler to Your Data Fetching Functions

Alright, you have mdx-bundler in your blogs packages. Now, we need to integrate it into our data fetching functions. This post assumes you already have a data fetching utility function added to your Next.js project. If you don't, not to worry you can follow the helpful tutorial from Next.js that will help you get it set up. I followed this guide when setting my blog up so the code below should be mostly the same, except for a few different function names.

If you followed the Next.js guide then you should have a utility that finds your blog posts and the metadata (frontmatter) that comes with it. Below is what this utility might look like (the functions have been shortened as they match the Next.js tutorial).

JS
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { bundleMDX } from "mdx-bundler";
const blogDirectory = path.join(process.cwd(), "blog");
export function getBlogPostData() {
// same as nextjs tutorial
}
export function getAllPostSlugs() {
// same as nextjs tutorial
}
export async function getPostData(slug) {
const fullPath = path.join(blogDirectory, `${slug}.mdx`);
const source = fs.readFileSync(fullPath, "utf8");
const { code, frontmatter } = await bundleMDX(source, {
xdmOptions(options) {
options.remarkPlugins = [...(options?.remarkPlugins ?? []), remarkGfm];
options.rehypePlugins = [...(options?.rehypePlugins ?? []), rehypePrism];
return options;
},
});
return {
slug,
frontmatter,
code,
};
}

In the above snippet, the magic happens in the getPostData function. Here is where we utilize the mdx-bundler package. There are a few things going on in this function and we are going to look into what is happening with the mdx-bundler part.

First, we import the bundleMDX into the file, so that we can use it in the getPostData function.

Within the function, we are destructuring each of your mdx files in the blogDirectory using the bundleMDX function.

The destructured code variable contains the contents of the mdx file things like your headings, images, links and, paragraphs. Importantly it also contains all the React components you have in the file.

Finally, the destructured frontmatter variable is the metadata for your post. It is the stuff at the top of a post that looks like the below.

MD
---
title: "MDX"
date: "2021-10-23T09:15:00-0400"
subtitle: "MDX beginners guide"
excerpt: "A look at how to make the most of MDX in your blog"
category: "coding"
---

If you want to know more about metadata and why it is important for any developer's blog SEO check out this guide . (I'm thinking about making a post on SEO for developers, reach out to me if that is something you'd be interested in)

The next part to note is where we are using the built-in xdm configuration, this allows you to add remark and rehype plugins. This can be really useful to style your code snippets or images. If you're interested you can see a list of available plugins remark here and rehype here.

Lastly, we return all the data we need to render our posts in a nice little object. Now, let's look at how to render our post and how to get the most of mdx-bundler.

Using Mdx-bundler in Next.js Blog Posts

Alright, so the first step we need in order to use mdx-bundler with our Next.js blog is done. Now let's see how to use it with our blog posts component so we can render them to the screen.

If you followed the Next.js tutorial then you should have a file in your posts directory called something like [id] or [slug] where you utilize the getStaticPaths and getStaticProps functions. On my blog I have called it [slug].js since it makes semantic sense to me.

In the[slug].js file, we need to import a few things. The first is the useMemo hook from the Reacts standard library. The second is getMDXComponent from the mdx-bundler package. Now your blogPost component should look similar to the below. We also need to import our data fetching functions, the ones you set up when following the Next.js tutorial.

Next, we are going to send the code data from our getPostData function to our client so that we can render our mdx files. We do this by first passing the code and frontmatter props to our BlogPost component (below).

The frontmatter prop will let us access our metadata by calling them like objects frontmatter.title.

Then, we use the code prop with the getMDXComponent function. Here we use the useMemo hook to prevent the component being created every time we render it which, really helps with performance. Now, our BlogPost component should look like the below.

JS
export default function BlogPost({ code, frontmatter }) {
const Component = useMemo(() => getMDXComponent(code), [code]);

The Component variable holds all the content of our blog post. We could finish here and render the post by calling <Component /> within our BlogPost component. Try it out to see how it renders.

JS
import { getMDXComponent } from "mdx-bundler/client";
import { useMemo } from "react";
import { getAllPostSlugs, getPostData } from "../../lib/utils/blogPosts";
export const getStaticProps = async ({ params }) => {
const postData = await getPostData(params.slug);
return {
props: {
...postData,
},
};
};
export async function getStaticPaths() {
const paths = getAllPostSlugs();
return {
paths,
fallback: false,
};
}
export default function BlogPost({ code, frontmatter }) {
const Component = useMemo(() => getMDXComponent(code), [code]);
return (
<>
<h1>{frontmatter.title}</h1>
<p>{frontmatter.description}</p>
<p>{frontmatter.date}</p>
<article>
<Component />
</article>
<>
)

If you view the post with the correct slug, it will now render all the elements within the mdx file to the screen. That is all you need to get your Next.js project to work with mdx-bundler. However, there is one more thing you can do that unleashes the full power of mdx-bundler. Let's see what that is now.

How to Bundle Up Your Components With Mdx-Bundler and Next.js

The absolute cherry on top of mdx-bundler that makes it a joy to work with is that you can "bundle" all of your reuseable components up to save you importing them in every mdx file.

On my blog I have a few components that get used in every post, things like a custom styled next/image component or customer link components. It would be annoying and prone to human error for me to import them into every blog post. Thankfully mdx-bundler is here to save that day.

To bundle up reusable components we can import them into our [slug].js. Once we have those files imported, we can pass them as props to our Component element.

JS
import PostImage from '../components/PostImage'
import InternalAnchor from '../components/InternalAnchor'
import PostRecommender from '../components/PostRecommender'
// ...
export default function BlogPost({ code, frontmatter }) {
const Component = useMemo(() => getMDXComponent(code), [code]);
return (
<>
<h1>{frontmatter.title}</h1>
<p>{frontmatter.description}</p>
<p>{frontmatter.date}</p>
<article>
<Component
componets={{
PostImage,
InternalAnchor,
PostRecommender,
}}
/>
</article>
<>
)

Now you can use these components when writing a post without even having to think about importing them.

If you're still having trouble getting it working with Next.js you can reach out to me and I'll see how I can help.

Picture of author

Peter Lynch

Web Developer & Bootcamp grad who wants to learn as much as he can check me out on twitter.

A Web Development Newsletter That Helps You Create

I'm learning heaps and having a blast doing it. Sign up for my newsletter where I'll share the things I've made and how you can make them too. Only real newsletters are going to last, all that other bullshit is here today & gone tomorrow.