Using MDX with Remote Content

One of the most common ways to use MDX in a website is to have the .mdx files together with the rest of the website code. But this has an issue because now any change you these files will need a full deploy, you can’t just open a CMS, change the content and see it live immediately.

But this shouldn’t need to be a white or black decision, there is a nice and simple way to use MDX from a remote source.

Let’s say our CMS give us some content like this:

# This is a note with MDX

<CustomComponent />

If we are using Next.js, we will need to install the package next-mdx-remote.

Then, inside the page we want to use MDX we need to import this

import renderToString from "next-mdx-remote/render-to-string";
import hydrate from "next-mdx-remote/hydrate";

In the page we will use them we need to create a components object with all the components we want to enable in our MDX files. They can be named as the HTML tags and in that case it will be used to render them (e.g. an h1 component will be used to render the # This is a note with MDX heading), if we use other names like our CustomComponent it will be available to use as in the MDX example above.

const components = { CustomComponent };

Inside our getStaticProps or getServerSideProps we will fetch our data and use renderToString.

async function getStaticProps({ params }) {
  // this is fetchnig from Collected Notes
  const note = await cn.read(CN_SITE_PATH, params.path.join("/"));
  // here we parse the front matter and get the content and data
  const { content, data } = matter(note.body);
  // here is where we parse our content to be usable as MDX
  const body = await renderToString(content, { components });
  // and at the end return our note, frontm atter data and body as props
  return { props: { note, body, data }, revalidate: 1 };
}

Then, inside our page component we can use hydrate with the body we got as props.

function Article({ note, body, data }) {
  // here we hydrate the body with our components
  const content = hydrate(body, { components });
  // and then we can render it however we want
  return (
    <>
      <Header {...note} {...data} />
      <article>{content}</article>
    </>
  );
}

And we are done, we can create our CustomComponent inside our codebase and import it. The key part is that the component objects need to be the same in both renderToString and hydrate. If in the future we want to use a new component as part of our MDX content we will need to add it to the codebase and deploy it, which makes sense since this is actual code and not only plain content.


I used to have a bunch of .mdx files in the repository of my blog, each article was a .mdx file with the front matter of the article; after being tired of taking a few minutes per change I wanted to migrate to a CMS, I decided to go with Collected Notes which has Markdown and front matter support, aside of other nice features like a native app for Mac and iPhone which allowed me to write easily and even from my phone.

However, doing this change required me to give up on MDX. Thanks to Tailwind Typography it was not a huge issue, my main reason to use MDX was to render custom styles for the elements.

Thanks to next-mdx-remote now I can use Collected Notes as my CMS and still use MDX which is a great way to have the best of both worlds.