MDX-Bundler with remark-mdx-images and Nextjs

The nitty gritty of getting remark-mdx-images to function with mdx-bundler and nextjs is the options set on the bundleMDX call.

I am going to break down this into three seperate parts.

First is the cwd setting. This is required for us to set in order for mdx-bundler to find and parse the markdown files.

Second is the xdmOptions setting. This is straight forward and requires no customizations. We can re-use the example provided by mdx-bundler github.

Third is the esbuildOptions setting. This one is where I found the most customizations required. For loader, and write, we can utilize the standard example from mdx-bundler. For the others we require customization

  • outDir should map to the location that our web framework wants to see the static file. We are using nextjs so that will be {projectRootDir}/public/*. I am using {projectRootDir}/public/images/posts/{slug} as a matter of organization. It could technically be anywhere under the {projectRootDir}/public/* as long as we map it correctly with the public path attribute. Since we are writing files in this process that we don't want stored in git, we should add this folder to our .gitignore

  • publicPath should map the URL that our web framework expects static files to resolve to. We are using nextjs so that path, based on what we did in outDir, will be /images/post/{slug}.

With these combined, we may get an implementation of fetching a single MDX post that looks like something below. This will allow us to colocate images next to the markdown file they are used in.

import path from "path";
import { bundleMDX } from "mdx-bundler";
import { remarkMdxImages } from "remark-mdx-images";

const POSTS_PATH = path.join(process.cwd(), "_posts");
const IMAGES_ROOT_PATH = path.join(process.cwd(), "public");

export async function getSinglePost(slug: string) {
  const imagesUrl = `images/posts/${slug}`;
  const fileName = checkForFolderOrFile(slug);
  const source = getSourceOfFile(fileName);

  const postsFolder = path.join(process.cwd(), "_posts");

  const { code, frontmatter } = await bundleMDX(source, {
    cwd: path.dirname(path.join(POSTS_PATH, fileName)),
    xdmOptions: (options) => {
      options.remarkPlugins = [
        ...(options.remarkPlugins ?? []),
        remarkMdxImages,
      ];
      return options;
    },
    esbuildOptions: (options) => {
      options.outdir = path.join(process.cwd(), "public", imagesUrl);
      options.loader = {
        ...options.loader,
        ".png": "file",
      };
      options.publicPath = `/${imagesUrl}`;
      options.write = true;
      return options;
    },
  });
}

home