Guide to Using Mdx-bundler With Next.js

March 4, 2022 (2y ago)

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 decided to use MDX for my blog posts.

MDX (which stands for Markdown Extension) allows us to import custom React components into our blog posts. I, personally, use it for things like image styling, code blocks, and anchor tags.

To use MDX with Next.js you need to use a seperate package, for my needs I went with 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.

The aim of this post is to help you incorporate mdx-bundler into your Next.js blog. If you want to know how to style your MDX codeblocks you can see my post here. So let's get into it, starting at step 1, installation.

Installation

I've followed the steps at the offical GitHub repo. Run either of the two commands below depending on what package handler you use.

npm install --save mdx-bundler esbuild

// OR

yarn add mdx-bundler esbuild

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. I followed this guide when setting up my blog, 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).

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: source, {
    xdmOptions(options) {
      options.remarkPlugins = [...(options?.remarkPlugins ?? []), remarkGfm];
      options.rehypePlugins = [...(options?.rehypePlugins ?? []), rehypePrism];
      return options;
    },
  });

  return {
    slug,
    frontmatter,
    code,
  };
}

There are a few things going on in this function, but we are going to mainly look at the mdx-bundler part. In the above snippet, the magic happens in the getPostDatafunction, which is where we utilize the mdx-bundler package.

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.

---
title: "MDX"
date: "2021-10-23T09:15:00-0400"
subtitle: "MDX beginners guide"
description: "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 here.

The next part to note is where we are using the built-in xdm configuration. This allows you to add remark and rehype plugins, which 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. However, please note that these are optional and you don't need them to use MDX, feel free to remove them if you're not going to use them.

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]. These are 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.

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.

// [slug.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 having to import 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 my codeblock 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.

// [slug.js]
import RoundedImage from '../components/RoundedImage'
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={{
                RoundedImage,
                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.