Omkar Narayankar

How to setup a dev blog using Next.js and next-mdx-remote.

20-Feb-2022

Tutorial

Not another Next.js and MDX blog again...

Yeah, I know. But hear me out, I am sure this'll probably be the last blog you'll be needing in order to set up your own blog with all the cool benefits of MDX.

Prerequisites

Before going on any further, I am expecting that you at least have a basic understanding of Next.js and MDX beforehand. If you don't, I'll highly recommend you check them out first.

Why next-mdx-remote?

A very good question. Why to choose next-mdx-remote when there are also other ways to implement MDX. Is it really better?

So, the simple answer for me is simplicity. It's easy to set up and also provides you with all the benefits of MDX you may need. Actually, before settling on this, I was using the official @next/mdx package to set up my blog, but with @next/mdx I was having trouble to pass the metadata along with my blogs.

There is also the next-mdx-enhanced package again by HashiCorp, but they themselves recommend next-mdx-remote for speed and scaling purposes.

So, how did I do it?

First, install all the packages we'll be needing using npm.

npm i create-next-app next-mdx-remote gray-matter

Create a new Next.js project with the create-next-app command.

npx create-next-app mdx-blog

Then change the folder structure like so, the highlighted parts are what we added.

mdx-blog
|-- blogs
| └-- first-blog.mdx
| |-- components |-- pages
| |-- blog
| | └-- [blogName].js
| | | |-- _app.js | |-- index.js
| └-- blogs.js
| |-- public
|-- MDX.js
|-- next.config.js |-- package-lock.json |-- package.json └-- README.md

The blogs folder in the root directory will be the folder holding all of our .mdx files (blogs).

For example, This is what this blog's .mdx file looks like inside my blogs folder. The portion separated by hyphens is our yaml metadata which we'll be accessing later on, also referred to as front matter.

how-to-setup-a-dev-blog.mdx
---
title: How to setup a dev blog using Next.js and next-mdx-remote.
date: 20-Feb-2022
category: Tutorial
description: Simple tutorial to setup your own dev blog using Next.js and next-mdx-remote.
author: Omkar Narayankar
---

...

#### Prerequisites

Before going any further, I am expecting that you atleast have a basic understanding about Next.js and MDX before hand.
If you don't, I'll highly recommend you check them out first.

- ###### [Next.js](https://nextjs.org)
- ###### [MDX](https://mdxjs.com)

...
Displaying all of the blogs at once

Now, let's go about displaying all the blogs we have in the blogs folder on our website.

With the power of Next.js file system routing, the blogs.js file within our pages directory will be the one representing the blogs page on our web app and this is also where we'll display all of our blogs programmatically. In order to display the blogs, we'll be creating getBlogs() which will make use of the node filesystem to return all the blogs within our blogs directory along with their front matter. But, along with it we are also passing a link, which is nothing but the name of the file. Make sure that you use this link to route to the respective blog page as the routes are going to be predefined using the same filename later in this tutorial. To read the front matter we are using a package we installed earlier called gray-matter which parses the metadata from the file content and returns it as data.

MDX.js
const fs = require("fs");
const path = require("path");
import matter from "gray-matter";

export const getBlogs = () => {
  let blogs = [];
  const files = fs.readdirSync(path.join(root, "blogs"));

  if (files) {
    files.forEach((file) => {
      if (path.extname(file) == ".mdx") {
        const source = fs.readFileSync(path.join(root, "blogs", `${file}`), {
          encoding: "utf-8",
        });
        const { content, data } = matter(source);
        blogs.push({ ...data, link: file.replace(".mdx", "") });
      }
    });
    return blogs;
  } else {
    return null;
  }
};

Now, all we have to do is to call getBlogs() within getStaticProps() in the blogs.js file and pass the returned blogs to the page component as a prop, like so

blogs.js
export const getStaticProps = () => {
  const blogs = getBlogs();

  return {
    props: {
      blogs,
    },
  };
};

So now, we have a statically generated page which will display all of our blogs at once by fetching them beforehand.

I am leaving the UI upto you and how you want to use this metadata to display your blogs.

Displaying individual blogs

To do this, we'll be needing a statically generated dynamic route, which will handle all of our blog routes. The routes will be predefined with the blog's filename as the query params. We'll later use this filename to parse the respective .mdx (blog) file, convert it to javascript and then display the blog on our page.

Sounds simple, right ? Well, it is simple with Next.js .

First, we'll be creating getPaths(), which will read the blogs directory and push the filename of every file (blog) to the url params object which Next.js requires in order to pre define all of the routes.

And, also getFileData() which just retrieves the file data and returns it.

MDX.js
export const getPaths = () => {
  let paths = [];

  const files = fs.readdirSync(path.join(root, "blogs"));
  if (files) {
    files.forEach((file) => {
      if (path.extname(file) == ".mdx") {
        paths.push({ params: { blogName: file.replace(".mdx", "") } });
      }
    });
    return paths;
  } else {
    return null;
  }
};

export const getFileData = (fileName) => {
  const data = fs.readFileSync(path.join(root, "blogs", `${fileName}.mdx`), {
    encoding: "utf-8",
  });
  if (data) {
    return data;
  } else {
    return null;
  }
};
Finally, the magic of next-mdx-remote

Till now, we were dealing with everything but next-mdx-remote, finally the time has come.

Now, all we have to do is call the functions that we made earlier within getStaticPaths() and getStaticProps() like so,

blog/[blogName].js
import matter from "gray-matter";
import { serialize } from "next-mdx-remote/serialize";
import { MDXRemote } from "next-mdx-remote";
import { getFileData, getPaths } from "../../MDX";

const Blogs = ({ mdxSource, frontMatter }) => {
  return (
    <>
      <h1>{frontMatter.title}</h1>
      <MDXRemote {...mdxSource} />
    </>
  );
};

export default Blogs;

export const getStaticProps = async (context) => {
  const { blogName } = context.params;

  const source = getFileData(blogName);
  const { content, data } = matter(source);
  const mdxSource = await serialize(content);

  return {
    props: {
      mdxSource,
      frontMatter: data,
    },
  };
};

export const getStaticPaths = () => {
  const paths = getPaths();

  return {
    paths,
    fallback: false,
  };
};

Basically, we are generating all the blog routes beforehand with getPaths() and passing the filenames along with it as the query params. Then whenever a user requests a particular blog, he will be redirected to the respective route and the filename of that .mdx (blog) file will be passed as the query params to the getStaticProps() method. After receiving the filename, we will be using it to get the file content using getFileData() and then passing the result to matter() exposed by gray-matter, which will first seperate the frontmatter as data and the actual markdown as content. Once we have the markdown part seperated we can pass it to serialize() exposed by next-mdx-remote, who does all the heavy lifting and converts our mdx to javascript. But, in order to actually display the jsx we need to make use of the <MDXRemote/> component and pass it the output of serialize. The parsed frontmatter also is now available to us in the page component, thanks to gray-matter.

Extending MDX

Although, we have succesfully set up our MDX blog, MDX could be made much more powerful with the use of plugins.

Refer the next-mdx-remote github to learn more the use of plugins and how awesome things could be done using MDX.