Black Sheep Code
rss_feed

How to configure Remix and mdx-bundler (with file system access)

Published:
warning

The MDX Bundler solution requires server-side file system access, and as such is not suitable for serverless application deployments using services like AWS Lambda or Netlify.

If you need a solution for a tool like Netlify, see this post.

This post outlines a process I took to improve this blog, although I ultimately abandoned this approach due to the requirement to have access to the file system.

Starting Point - The Old Way - Configuring via Remix's mdx option.

Initially I had followed Remix's MDX guide.

We declare .mdx files in our Remix routing structure. Remix's out-of-the-box tooling can handle the the MDX content and at build time converts it to JavaScript where it is treated just like any other ordinary React route component.

Conveniently, YAML frontmatter headers are also supported.

Some configuration is required - we declare Remark and Rehype plugins in the remix.config.js.

The Problem

The problem I had is mostly around the usage of frontmatter - I had no control of how the frontmatter behaved with my application; I could only use the functionality as provided with the out-of-the-box tooling - adding titles, social meta tags etc.

One annoying issue I had was that twitter's social meta tags required their own twitter prefix (eg. og:twitter:title), even though this was going to be the exact same as the other socials, I had to go through and specially add frontmatter items for twitter on every post.

But mostly, I wanted to be able to group items in a series - and provide links to the next item in the series etc, all without having to maintain those links everywhere.

Note the nice series box at the top of this page.

Additional functionality might include including an author tag and creation dates.

The Solution

warning

This solution is works for mdx-bundler@9.2.1. See the following issue:

info

The code blocks in this post come from this very simple Remix application repository here.

A recommended solution by Remix themselves is to use mdx-bundler.

At first I looked at how Kent C. Dodds' builds his blog. But his configuration is complex (eg. see here).

One of the top search results for 'remix mdx-bundler' gives us this Oldweb2 blog post, which is a lot simpler. However, this post is quite out of date. However, it was a useful starting point in understanding the general strategy of using mdx-bundler.

Move all of your MDX files out of the routes directory

You are no longer going to use Remix's out-of-the-box file routing for the blog posts. I moved all my posts from ~/app/routes/test/*.mdx to ~/app/blog-posts/test/*.mdx.

Update your TSConfig if you haven't already

The module property will allow us to do dynamic imports later.

Declare .server.ts utility files

The .server.ts file convention causes Remix to not include this code in client bundles - which is important as these use node packages.

Create a post.tsx utility for parsing the MDX.

Here we load our MDX files at run time and use MDX Bundler to parse them as components and as frontmatter.

Create a slug handler

We now handle any requests for our blog posts with $.tsx handler. We just pass the entire slug to our utility function, and ask it to return the component and frontmatter.

Update any relative imports

Any relative imports may no longer work, as the imports will be relative to the application root, rather than the file doing the importing. So update them accordingly.

Update a pages metadata based on frontmatter

Your Remix meta function can use the frontmatter that the loader returned. Use this to update the page meta tags.

Create a post header based on the post frontmatter

We can use the loaded frontmatter to create whatever other content (such as tags, author, post title) at the top (and/or bottom) of our post.

Create home page list of blog posts

Here we get access the frontmatter across all posts to create a table of contents on our home page.


And there you have it!



Questions? Comments? Criticisms? Get in the comments! 👇

Spotted an error? Edit this page with Github