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 hassle-free
and with all the cool benefits of MDX.
Prerequisites
Before going on any further, I am expecting that you at least have a basic understanding about 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 reasons.
So, how do 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 project with the create-next-app command.
npx create-next-app mdx-blog
Then change the folder structure like so,
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.
---
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.
// filename : 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
// filename : 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, getFileData() which just retrieves the file data and returns it.
// filename : 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,
// filename : 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 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.
Top comments (0)