Create a blog with markdown-generated posts, with support for custom React components and syntax highlighting on code snippets.
Motivation
I've wanted to create a blog for awhile. When I decided to start building this blog a few weeks ago, I knew that I wanted to use Markdown for the following reasons, among others:
It's easy to write a new blog post
I don't have to worry about an external Content Management System
I can turn a note from Obsidian into a blog post seamlessly. I use Obsidian to take notes at work and in my personal life.
I wanted to use React, and I've watched some YouTube videos about Next.js, which takes advantage of some cool new features like React Server Components which I'd like to try out. Also, the built in router is really nice.
MDX allows you to transform markdown into HTML - pretty cool, but nothing special. But it also allows you to insert JSX into your markdown (hence the name), so you can render complicated React components in the middle of your markdown blog post.
Tailwind describes itself as a 'utility-first' CSS framework. I won't go into detail here, but I highly recommend using Tailwind in front-end projects. If you already know CSS, there's very little to learn to get started.
This is a plugin for Rehype, a module which relies on plugins to transform HTML. We'll use Rehype Pretty Code to create code blocks styled by language.
Note: This tutorial will not cover how to configure rehype-pretty-code within Contentlayer. Some helpful resources I used to accomplish this include Contentlayer's makeSource docs and a blog post from Delba Oliveira.
Building the blog
Let's begin by creating a new nextjs application
If you check out the Contentlayer documentation, you might notice that the first section of my tutorial here overlaps heavily with their Getting Started page. If you're looking for more detail on specific properties or configurations, check out their docs.
Open the project in your favorite IDE and replace the contents of next.config.js with the following
Next, add the following lines to tsconfig.json
Now add the following line to your .gitignore
Creating a schema for our posts
First, we need to install Contentlayer
Because we're using Contentlayer, we need to define a schema for our posts in a file called contentlayer.config.ts in the root of our project.
There's a few important things to note here. First, I'm defining specific fields to be required at the top of my MDX files in order for Contentlayer to process them. These fields are completely optional, but we'll see their utility soon.
Also, I'm placing my posts in a folder called posts, which is inside the src folder in the root of my project. You should replace the contentDirPath property in the final line of the file to the path to your folder of MDX files.
Create your first post
Let's make our first post. To give you an idea of the output, I'll show you the first few lines of the markdown file used to generate this post
Now that we have a post, let's create a home page where users can see a list of all of our posts at src/app/page.tsx
We can run npm run dev to start a local server and then visit localhost:3000 to see our first page. If we click on the link for our first post, we'll get a 404 error, since we never created a page in our application that renders our post's content. Let's create that now at src/app/posts/[slug]/page.tsx
Now try navigating to the post again. You'll see a fully rendered version of the markdown you wrote! If the header looks like a paragraph, that's because we haven't styled it yet. If you'd like, you can set the font size for an h2 element in src/app/globals.css to make it stand out.
Adding JSX to MDX
At this point, everything should be working. So we have our markdown files correctly being transformed into HTML, but we still need to figure out how to insert custom react components into our blog posts. There's three steps here.
Create a react component
Import the component to your markdown file below the metadata section, and call the component
Add the component to the mdxComponents object in src/app/posts/[slug]/page.tsx
Okay, so first, let's build a really simple React component
Next, let's add this <Test> component to our MDX file
Finally, let's update our the page.tsx file that renders each of our blog posts
Now if we load our app and select our post, we should see the button! If you open up a console in developer tools and click the button, you'll see the message logged. If you want to understand the purpose of the 'use client' directive we included at the top of our <Test> component, check out this page from the Next.js docs.
At this point, everything should be set up for you to start writing new blog posts
Bells and Whistles
One of the cool features of MDX is that we can easily transform any HTML component that would be produced by MDX into something different. As an example, let's transform all of the <a> tags on our site.
First, let's understand what's going on right now. In our markdown, we can insert a link with the following syntax
Currently, MDX transforms our markdown into an <a> tag. Now, let's see how we can transform all of those into Next.js Link components (<Link>)
That's it! Adding this line to our mdxComponents Object, which gets passed into our call to <MDXContent>, tells MDX to transform all of the <a> tags to <Link> React components.
Using the same method with an <img> tag, we could easily format all of our images the exact same way.
Hosting
Personally, I'm hosting this site using Vercel, which is probably the easiest option for a Next.js site, although I've used it for plenty of other sites in the past too! It's completely free to use (unless your blog becomes really popular) - and you can configure your domain to point at Vercel's servers very easily.