Building this Blog

2024-10-18

Building a Blog with Next.js, Markdown, and Dynamic Routing

In this post, we’ll walk through the process of building a blog using Next.js and Markdown. This project incorporates dynamic routing, category-based organization, and front matter for managing metadata. By the end of this blog, you’ll understand how to set up a flexible and scalable blog using these technologies.

Setting Up the Folder Structure

We started by organizing our blog posts into categories by creating folders within the /posts directory. Each folder represents a category, and each markdown file within a folder is a blog post.

Folder Structure Example

/posts
  /nextjs
    - first-post.md
  /javascript
    - intro-to-js.md

Each post is written in Markdown and contains front matter at the top, which allows us to store metadata like the post’s title, description, and date. Here's an example of the front matter in a markdown file:

---
title: "Building a Blog with Next.js and Markdown"
date: "2024-10-18"
---

This structure allows us to easily categorize and organize blog posts while keeping everything readable and manageable.

Reading Metadata from Markdown Files

To parse the front matter and content from the markdown files, we used the gray-matter package. This allows us to extract the metadata (such as title, description, and date) and the actual content of each post.

We updated the getAllPosts function to read and parse all markdown files:

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');

export function getAllPosts() {
  const categories = fs.readdirSync(postsDirectory);
  const allPosts = [];
  const categoryMetadata = [];

  categories.forEach((category) => {
    const categoryPath = path.join(postsDirectory, category);
    const postFiles = fs.readdirSync(categoryPath);

    const posts = postFiles.map((fileName) => {
      const filePath = path.join(categoryPath, fileName);
      const fileContents = fs.readFileSync(filePath, 'utf8');
      const { data: frontMatter, content } = matter(fileContents);

      return {
        slug: `${category}/${fileName.replace(/\.md$/, '')}`,
        category,
        frontMatter,
        content,
      };
    });

    allPosts.push(...posts);

    categoryMetadata.push({
      category,
      title: posts[0]?.frontMatter.categoryTitle || category,
      description: posts[0]?.frontMatter.categoryDescription || '',
    });
  });

  return { allPosts, categoryMetadata };
}

By using gray-matter, we’re able to pull in the post’s front matter along with the content. This makes it easier to display post titles, descriptions, and dates dynamically across the site.

Dynamic Routing with Next.js

Next.js provides a powerful routing system that allows us to create dynamic pages based on the category and post slug. For this project, we structured our routes like this:

  • /blog/[category]/[slug]: The dynamic route for blog posts. Each post is stored in a specific category, and the slug is the post's filename without the .md extension.

Example of getStaticPaths and getStaticProps

Here's how we dynamically generated paths for our blog posts:

export async function getStaticPaths() {
  const { allPosts } = getAllPosts();

  const paths = allPosts.map((post) => ({
    params: {
      category: post.category,
      slug: post.slug.split('/').pop(),
    },
  }));

  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({ params }) {
  const { category, slug } = params;
  const { allPosts } = getAllPosts();
  const post = allPosts.find(
    (post) => post.category === category  &&
      post.slug.split('/').pop() === slug
  );

  return {
    props: {
      post,
    },
  };
}

With getStaticPaths, we generate all possible paths for each blog post based on its category and slug. Then in getStaticProps, we fetch the post’s data for rendering based on these parameters.

Displaying Categories and Posts

On the homepage, we linked to each category rather than displaying all posts. This keeps the homepage clean and organized, with each category having its own page that lists the posts under it.

export async function getStaticProps() {
  const { categoryMetadata } = getAllPosts();

  return {
    props: {
      categories: categoryMetadata,
    },
  };
}

export default function BlogHome({ categories }) {
  return (
    <LayoutWrapper>
      <h1>Blog Categories</h1>
      <div>
        {categories.map((category) => (
          <Link 
            href={`/blog/${category.category}`}
            key={category.category}>
            <div>
              <h2>{category.title}</h2>
              <p>{category.description}</p>
            </div>
          </Link>
        ))}
      </div>
    </LayoutWrapper>
  );
}

This structure allows for a clean, scalable blog that organizes content by categories and provides an easy way to navigate between posts and categories.

Conclusion

Building a blog with Next.js and Markdown is a powerful and flexible way to manage content. Using dynamic routing and front matter allows for easy customization, scalability, and the ability to categorize posts. Whether you're building a simple personal blog or a large content site, this setup provides the flexibility needed for growth and customization.