Introduction to MDX

Nov 05, 2020☕ 7 min read

Hello world! For the first post of my blog I wanted to go a little meta and explain how I created this site.

This website is a React application which uses Next (or Next.js or NextJS) and MDX for the posts themselves. I'm also using TailwindCSS for the styles, but I won't cover that in this post.

Context

Next is a hybrid static site generator with server rendering (and a lot of other features), and MDX is a JSX-in-markdown loader, parser, and renderer.

There are several reasons why I'm using Next, mainly because of its blazing performance. It uses server side rendering and crazy optimization to bundle your web application and serve a light website to you. I may write more about Next in the future, let's see.

So far I knew the stack for building the site itself (React with Next), but I had to decide about the posts (because writing Next pages for posts was not an option).

When I thought about how I would create it and specially where would I put my posts, I considered Gatsby and GraphCMS. These are great options, but I always loved keeping my content close.

Then I discovered this great article about creating a blog with Next and MDX, just what I wanted to do! Writing the posts as markdown is easy, it allows me to write them from my phone or iPad, and it's a workflow I enjoy, which I believe is crucial to keep any kind of consistency on writing.

Let's start building

I already had a Next app bootstraped with create-next-app, but I still needed to do some config to make the blog/MDX part.

For the configuration of the repository, you can check out MDX's suggested Next setup, with the mdx-js loader to parse the .mdx files. You can also check this post, as I cover my Next configuration a bit more deeply.

Apart from the base configuration, there are several key matters here:

  1. There are some components that are required here, but the main one is the BlogPost component, which renders its children inside an article, and a post header.
  2. Inside the pages folder, usually reserved for Next pages (routes), I created a blog folder with a folder inside for each post.
  3. The magic for this site is the script getAllPosts.js.

I'll explain each one:

1. Blogpost component (and others).

In every MDX post, you need some code at the begining of the file to make it work, transpile, and render. Apart from exporting a meta object I'm using to render some stuff like the read time or description, I'm exporting the following function component:

export default ({ children }) => <BlogPost>{children}</BlogPost>;

MDX provides me with a children object that contains the elements of the article (<p>s, images, etc.), which I then wrap around a Blogpost component that has an article header (title, date, read time) and an <article> tag that wraps the children. If this sounds confusing, I'll summarize: what you see in every article is a <Layout> component (inside Next's _app), then the <Post> component (with the PostHeader and the article itself), and then everything else from the MDX file. If it helps, you can check the code here!

2. Blog page and its folders.

The way Next does routing is based on files and folders in the pages directory. This means that, in order to create a /hello page, I just need to create a hello.js file or /hello/ folder inside /pages, in the root of the project.

So, the first thing I did for the blog was creating this blog folder, where the index.js for the blog lives (the post list you see on /blog). Then, I have a folder for every post, with images related to that post, and an index.mdx file for the content (the post itself).

3. The real magic: getAllPosts.

This file that lives in the utils folder of my repo, uses the context module API.

It has a (relatively) simple code:

// this function processes everything inside a given folder
function importAll(r) {
  return (
    r
      // then takes the list of files/folders
      .keys()
      // reverses it (to show new articles first, as they're ordered alphabetically)
      .reverse()
      // and for each folder (post) returns an object with:
      .map((fileName) => ({
        // 1. a link: the post route/path (without the /index.mdx bit!)
        link: fileName.substr(1).replace(/\/page\.mdx$/, ''),
        // 2. the module itself, this is the code that is imported/exported
        module: r(fileName),
      }))
  );
}

// we call this function with the /pages/blog folder, to import every .mdx inside.
export default importAll(require.context('../pages/blog', true, /\.mdx$/));

The list of objects ({link, module}) that this function returns is what I'm using to render the post list you see in /blog. That page component is just rendering each "post" object into a Preview component to render the cards.

postList.map((post) => <Preview key={post.link} post={post} />);

Other stuff I'm using

I use highlight.js to add syntax highlight to code blocks, several image optimization plugins such as next-optimized-images or remark-images.

Repository

The code for this website is open source and you can find it here.

dfr-web

I hope this article can help you set up your blog using Next and MDX! Apart from checking the code, you can contact me on twitter if you have any doubts.

See you on the next article, and many thanks for reading!