I made my personal blog with React + Next + MDX

Luis Locon

April 03, 2021

0 views

7 min read

I have been working on a personal blog for a long time. I always delayed because I think is hard to choose the tech stack. You know you have many choices, an example of this is if you write in google How create a blog? you surely going to find many results. Like how expert bloggers uses a dedicated product to handle content like wordpress or ghost, because is the easy way to configure one in minutes. In other case if you search Start a Dev Blog new result appears, as developer I always love the challenge to build things, for that and others reason, I decided to code it. So the next question to awnser was What tool or tools to use?

After of watching a lot of videos of how others build their own blogs, I think this a trend for developers, and inspired by @rauchg's Blog (He has a blog with the most minimal way he can code it so I like it). I decided to create my own in that way, as simple as I can.

In short, I chose these three tools, React + Next.JS + MDX. In the next minutes I'll explain what each of them is.

What is React?

React ⚛️ is a popular library to create amazing/interactive User Interfaces, one of the most valuable things React has is the VirtualDOM. This feature makes it possible to react know when a node of the DOM is going to change and re render the app efficently with just the components in the meantime.

React ⚛️ also has this component paradigm, wich implies the way an UI is build. This is like playing legos, a component is a block, and the sum of the blocks would be a complete UI. All components are part of the UI, and can be reused anywhere of the app. The components can be state or stateless, they also have lifecycles either and this an important feature for them, because that's how the behavior of it is handled.

What is Next.Js?

For me this is the easiest way to start a React App and have an amazing development experience 🤯. But, What is Next? Well, in words of them: Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed.

The No config needed is a greate feature because is true. You can also configure a few things (If you want), but if you just want to start developing, you just need the dependencies and run the script and that's it.

What is MDX?

MDX is an authorable format that lets you seamlessly write JSX in your Markdown documents. You can import components, such as interactive charts or alerts, and embed them within your content. This makes writing long-form content with components a blast 🚀.

This extract if from the documentation of MDX, but in simple words: MDX is the solution of how we can write Markdown and also use the benefits of components with React.

Setting up Next with MDX

After that introduction this is the steps I follow to built this project

First create a next app

$ yarn init -y
$ yarn add next react-dom react

After that, we need to write a necesary scripts to run the app, in the package.json we going to write the scripts part

{
"name": "loconluis-bloggin",
"version": "1.0.0",
"description": "Blog and personal site",
"main": "index.js",
"author": "Luis Locon <luis.locon@gmail.com>",
"license": "MIT",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^10.0.7",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}

How Next works we should create a directory called pages with index.js file. In the next image we will see how I order the project

|-- app
|-- <All components files>
|-- entries
|-- <All the markdown files with extension .mdx>
|-- lib
|-- mdx.js
|-- public
|-- <Assets and icons>
|-- styles
|-- style.css
|-- pages
|-- index.js
|-- _app.js
|-- _document.js
|-- blog
|-- [slug].js
|-- package.json

For practicality we going to omit what is inside on entries, app, and public directories. In pages Next handle the routes of the application if you want to create a dynamic route you can write the file like [slug].js.

In this moment we setup the static views of the app, we can reach it pages trought URLS. Now is moment to setup MDX

We need to add new dependencies

$ yarn add next-mdx-remote gray-matter

We'll to handle all the configuration in the lib/mdx.js file

// mdx.js
import fs from "fs";
import matter from "gray-matter";
import renderToString from "next-mdx-remote/render-to-string";
import path from "path";
// This a file with a JSON of styled components to use with markdown elements
import MDXComponents from "../app/components/MDXComponents";
const root = process.cwd();
// Used to load paths of the files
export async function getFiles(type) {
return fs.readdirSync(path.join(root, "entries", type));
}
// Obtain a file from a Route and return a ready content and useful information
export async function getFileBySlug(type, slug) {
const source = slug
? fs.readFileSync(path.join(root, "entries", type, `${slug}.mdx`), "utf-8")
: fs.readFileSync(path.join(root, "entries", `${type}.mdx`), "utf-8");
const { data, content } = matter(source);
const mdxSource = await renderToString(content, {
components: MDXComponents,
});
return {
mdxSource,
frontMatter: {
wordCount: content.split(/\s+/gu).length,
slug: slug || null,
...data,
},
};
}
// Get the front matter info from files
export async function getAllFilesFrontMatter(type) {
const files = fs.readdirSync(path.join(root, "entries", type));
return files.reduce((allPosts, postSlug) => {
const source = fs.readFileSync(
path.join(root, "entries", type, postSlug),
"utf8"
);
const { data, content } = matter(source);
return [
{
...data,
slug: postSlug.replace(".mdx", ""),
},
...allPosts,
];
}, []);
}

After that it's just calling the functions inside the files from the pages

pages/index.js

import Head from "next/head";
import MainPage from "../app/MainPage";
import { getAllFilesFrontMatter } from "../lib/mdx";
export default function Index({ posts }) {
const _posts = sortPost(posts);
return (
<>
<Head>
<title>Blog by Luis Locon</title>
</Head>
<MainPage posts={_posts} />
</>
);
}
const sortPost = (posts) => {
const _posts = posts.sort((a, b) => {
return new Date(b.publishedAt) - new Date(a.publishedAt);
});
return _posts;
};
export async function getStaticProps() {
const posts = await getAllFilesFrontMatter("/");
return { props: { posts } };
}

pages/blog/[slug].js

import hydrate from "next-mdx-remote/hydrate";
import { getFiles, getFileBySlug } from "../../lib/mdx";
import Head from "next/head";
import BlogLayout from "../../app/Layout/Blog";
import MDXComponents from "../../app/components/MDXComponents";
export default function BlogPage({ mdxSource, frontMatter }) {
const content = hydrate(mdxSource, {
components: MDXComponents,
});
return (
<>
<Head>
<title>{frontMatter.title} - Blog by Luis Locon</title>
</Head>
<BlogLayout frontMatter={frontMatter}>{content}</BlogLayout>
</>
);
}
export async function getStaticPaths() {
const posts = await getFiles("/");
return {
paths: posts.map((p) => ({
params: {
slug: p.replace(/\.mdx/, ""),
},
})),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const post = await getFileBySlug("/", params.slug);
return { props: post };
}

And that's it, now you can create new files .mdx in entries. And the application automatically will recognize the file and going to list and render it.

You can see the code here.

I Hope you can find this useful. Any doubt you can reach me via twitter @LoconLuis.