Beginners Guide to Using mdx-bundler With Your Next.js Blog
Tags: javascript, nextjs, mdx, tutorial
Published: 2021-12-15 02:50:37

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](https://mdxjs.com/) 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](https://github.com/hashicorp/next-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](https://www.peterlunch.com/snippets/next-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.

This post aims to do just that by breaking down how to use mdx-bundler with Next.js, if you want to know how to style your MDX codeblocks you can [see my post here](https://www.peterlunch.com/blog/prism-react-render-nextjs). 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](https://github.com/kentcdodds/mdx-bundler#installation). 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](url="https://nextjs.org/learn/basics/data-fetching) 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 <ExternalAnchor title="destructuring" url="https://hacks.mozilla.org/2015/05/es6-in-depth-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 [here](https://ahrefs.com/blog/seo-meta-tags/). (I'm thinking about making a post on SEO for developers, [reach out to me](https://twitter.com/thelynchpinau) 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](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins) and [rehype here](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins).

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] (https://www.peterlunch.com/snippets/next-image-styling) 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.