Technical note

Updating my personal site with NextJS and a blog

A personal narrative about why and how I updated my personal site with NextJS and MDX. It's meant as a reference for myself and others.

Chris Brett's avatar image
Chris Brett

Introduction

For a while now, I've had a bunch of articles written and stored in markdown format. I've wanted to display these on my personal site for quite some time, but I've put it off time and again. I decided to use this as an opportunity to deepen my understanding of Next.js through the implementation of a blog using MDX.

What is MDX?

Without diving too deep, as the official MDX documentation provides a thorough explanation: MDX simply blends JSX with markdown, enabling React components in markdown files. It's a great way to add interactivity to your articles without having to write a whole new component for each one.

Why NextJS and MDX?

Up until about a year ago, my site was built with static HTML, CSS, and JS. This was sufficient until the need for updates and redesigns emerged, revealing the cumbersome nature of my workflow. I'd implemented my CSS using the now defunct ruby-sass; I didn't need a database, I didn't need a server, I didn't need anything dynamic. I just needed a place to host information about me and my career so far.

However, when it came to adding new content or a new design, this was painful. ruby-sass was no longer maintained and I had to use node-sass instead. I had to compile my SCSS every time I made a change. I had to manually go into each file and update the HTML with new content. Suffice to say it wasn't much fun. My simple solution was to introduce ParcelJS.

So what changed, how did I get to a full Next.js build? Well, a little while back I'd been exposed to the Next framework at work and had been reading plenty of articles and watching YouTubers galore talk about React Server Components and the problems they solve. Diving a little deeper into the topic over the coming months, I found myself enjoying building side projects this way and it reminded me of the Ruby on Rails days. The ease of creating routes and connecting/interacting with a database directly felt easy.

Why MDX though? I wanted to keep my articles in markdown format, but I also wanted to be able to add interactivity to them. I didn't want to have to write a whole new component for each article; MDX seemed like a well-enough documented solution.

The reason I'm writing this article

Simply put, I just couldn't find a good enough example of how to use MDX with Next.js. I came across bits of info here and there. Read the documentation, but for whatever reason, it didn't just work out of the box like I'd hoped. I had to do a bit of digging and a bit of trial and error to get it to work. This article aims to serve as a reference for myself and others who might be in the same boat.

The setup

I'm not going to go into the setup of a Next.js project, there are plenty of articles and videos out there that do a better job than I could. This section assumes the existence of a Next.js project setup.

Installing the necessary packages

Begin by installing the necessary packages. Run the following command in your terminal:

npm i next-mdx-remote gray-matter
  • next-mdx-remote is a package that allows you to use MDX with NextJS.
  • gray-matter is a package that allows you to parse frontmatter from markdown files.

Setting up the blog route

Establish a blog route within your project directory, for example, src/app/(blog)/articles/[slug], and create a page.tsx file within this directory to house your blog post display logic.

Note: Don't worry that there are some functions for fetching article slugs and getting an article by slug that don't exist yet. We'll get to that in a next.

src/app/(blog)/articles/[slug]/page.tsx
1interface ArticlePageProps {2  params: {3    slug: string;4  };5}6 7export async function generateStaticParams() {8  return getAllArticleSlugs();9}10 11export default async function ArticlePage({ params }: ArticlePageProps) {12  const { articleFrontmatter, articleContent } = await getArticleBySlug(params);13 14  return (15    <article>16      <h1>This is our template for a blog post</h1>17    </article>18  );19}

Getting the article slugs

We need to get the slugs of all our articles so we can generate the routes for them. Create a file called getAllArticleSlugs.ts in the same directory as the page.tsx file (or anywhere you'd like) and add the following code:

src/app/(blog)/articles/[slug]/getAllArticleSlugs.ts
1"use server";2 3import fs from "fs";4import path from "path";5 6const CONTENT_DIR = path.join(process.cwd(), "src/articles");7 8export async function getAllArticleSlugs() {9  const files = fs.readdirSync(CONTENT_DIR);10  const slugs = files.map((file) => ({ slug: path.parse(file).name }));11 12  return slugs;13}

Getting the article content

Now we need to get the content of the article. Create a file called getArticleBySlug.ts in the same directory as the page.tsx file (or again anywhere you'd like) and add the following code:

src/app/(blog)/articles/[slug]/getArticleBySlug.ts
1"use server";2 3import fs from "fs";4import path from "path";5import matter from "gray-matter";6 7const CONTENT_DIR = path.join(process.cwd(), "src/articles");8 9export async function getArticleBySlug({ slug }: { slug: string }) {10  const file = path.join(CONTENT_DIR, `${slug}.mdx`);11  const markdownFileContents = fs.readFileSync(file, "utf-8");12 13  const { data: articleFrontmatter, content: articleContent } =14    matter(markdownFileContents);15 16  return {17    articleFrontmatter,18    articleContent,19    articleSlug: slug,20  };21}

There is some duplicated code here for the CONTENT_DIR variable. You could extract this to a separate file and import it into both files, but it's fine for demo purposes.

Creating the article markdown files

Now we need to create the article markdown files. Create a directory that relates to your CONTENT_DIR in the src directory (in this example src/articles/) and add a markdown file called hello-world.mdx with the following content:

src/articles/hello-world.mdx
1---2title: "Hello world!"3description: "My first blog post"4---5 6# Add your markdown content here7 8This is my first blog post. I hope you enjoy it.

Displaying the article content

Now we need to display the article content. Update the page.tsx file to the following, which will pull out the frontmatter and content from the article and display it.

src/app/(blog)/articles/[slug]/page.tsx
1...2 3export default async function ArticlePage({ params }: ArticlePageProps) {4  ...5 6  const { title, description } = articleFrontmatter;7 8  return (9    <article>10      <header>11        <h1>{title}</h1>12        <p>{description}</p>13      </header>14 15      <div>16        <MDXRemote source={articleContent} />17      </div>18    </article>19  );20}

There we have it, a very simple example of how to bring in content from markdown files and display it using MDX with NextJS. I hope this helps someone out there get started or just future me who's not done this in a while.

Conclusion

Of course, there is more to do, such as adding custom components to your markdown files and styling, but this should be a good starting point and I'll possibly cover those in separate articles. If it is helpful, the code for this example is similar to the code I used to build this site and you can see that for yourself on Github.