Building a Blog with Next.js and Markdown

Building a Blog with Next.js and Markdown

Learn how to create a powerful, fast, and SEO-friendly blog using Next.js and Markdown.

Introduction

Blogs are a fantastic way to share knowledge, insights, and experiences, and creating one can be an exciting project for any developer. With modern tools like Next.js and Markdown, building a blog has never been easier. Next.js, a powerful React framework, offers great features for server-side rendering (SSR), static site generation (SSG), and dynamic content handling. Markdown, a lightweight markup language, allows you to write content in a simple and readable format while retaining flexibility in structuring and displaying that content.

In this tutorial, we will walk through how to build a blog from scratch using Next.js and Markdown. We will cover everything from setting up your development environment to deploying the blog on a hosting platform like Vercel.


Main Content

1. Setting Up the Project

Before we begin writing our blog, we need to set up a new Next.js project. Here’s how:

  1. Create a Next.js app: Run the following command to create a new Next.js app using create-next-app.

     npx create-next-app@latest my-nextjs-blog
     cd my-nextjs-blog
    
  2. Install dependencies: We will use a package called gray-matter to parse the front matter (metadata) of our Markdown files and remark to transform the content into HTML.

    Install them with:

     npm install gray-matter remark remark-html
    
  3. Create directories: Organize the project structure. Inside your project, create a posts directory where we will store our Markdown files.

     mkdir posts
    

2. Writing Markdown Files

Markdown files are simple text files that can contain both content and metadata. For our blog posts, we will add metadata in the form of front matter at the top of each Markdown file. Here’s an example of what a Markdown file might look like:

Example: first-post.md

---
title: "My First Blog Post"
date: "2025-01-01"
description: "This is the description of my first blog post."
---

# Welcome to my blog!

This is my first post using Next.js and Markdown. It's easy to build and render!
  • The part between --- is the front matter, which contains metadata like the post title, date, and description.

  • Below the front matter is the actual content of the post written in Markdown.

3. Fetching Markdown Files in Next.js

Next, we need to read and fetch the Markdown files from our posts folder. This will allow us to dynamically load each blog post’s content. In Next.js, we will do this inside the getStaticProps function for static site generation.

Create a file lib/posts.js to handle loading the Markdown files:

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { remark } from 'remark';
import remarkHtml from 'remark-html';

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

export function getSortedPostsData() {
  // Get file names under the posts directory
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map(fileName => {
    // Remove the ".md" extension
    const id = fileName.replace(/\.md$/, '');

    // Read markdown file as string
    const fullPath = path.join(postsDirectory, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf8');

    // Parse the post metadata
    const { data } = matter(fileContents);

    return {
      id,
      ...data,
    };
  });

  // Sort posts by date
  return allPostsData.sort(({ date: a }, { date: b }) => {
    if (a < b) {
      return 1;
    } else if (a > b) {
      return -1;
    } else {
      return 0;
    }
  });
}

export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  // Parse the post metadata and content
  const { data, content } = matter(fileContents);

  // Use remark to convert markdown to HTML
  const processedContent = await remark()
    .use(remarkHtml)
    .process(content);
  const contentHtml = processedContent.toString();

  return {
    id,
    contentHtml,
    ...data,
  };
}

Explanation:

  • getSortedPostsData reads all the Markdown files and sorts them by date.

  • getPostData fetches the individual content of a post, processes the Markdown content using remark to convert it into HTML, and returns it.

4. Displaying Posts on the Home Page

To display the list of blog posts on the homepage, modify pages/index.js:

import { getSortedPostsData } from '../lib/posts';

export default function Home({ allPostsData }) {
  return (
    <div>
      <h1>My Blog</h1>
      <ul>
        {allPostsData.map(({ id, title, date }) => (
          <li key={id}>
            <a href={`/posts/${id}`}>
              <h2>{title}</h2>
              <small>{date}</small>
            </a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  const allPostsData = getSortedPostsData();
  return {
    props: {
      allPostsData,
    },
  };
}

Explanation:

  • We fetch all the blog post data using getSortedPostsData and pass it as a prop to the component.

  • Each blog post title is rendered as a link to the individual post page.

5. Displaying Individual Blog Posts

To display individual blog posts, create a dynamic page under pages/posts/[id].js:

import { getPostData } from '../../lib/posts';

export default function Post({ postData }) {
  return (
    <article>
      <h1>{postData.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
    </article>
  );
}

export async function getStaticPaths() {
  const paths = getSortedPostsData().map(post => ({
    params: { id: post.id },
  }));
  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({ params }) {
  const postData = await getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}

Explanation:

  • getStaticPaths generates paths for each blog post based on the Markdown file names.

  • getStaticProps fetches the post data using getPostData and renders it.


Examples/Case Studies

Example 1: A Simple Personal Blog

Consider a personal blog where each post showcases tips for web development. Using the structure above, you could easily manage and display multiple posts. Markdown makes it simple to write and format each post, and Next.js ensures fast page loading through static generation.

Example 2: A Developer’s Portfolio Blog

A developer could use the same structure to maintain a blog portfolio of personal projects and tutorials. Markdown allows you to easily create technical write-ups, and Next.js makes the site highly performant with static site generation.


Tips/Best Practices

  • Use Markdown Linting: To maintain consistency in your Markdown files, consider using a linter to enforce rules.

  • Optimize Images: If you’re embedding images in your posts, use Next.js' built-in next/image component for automatic optimization.

  • Customizing the Markdown Renderer: Extend the Markdown processing pipeline to handle additional syntax, such as custom components for code snippets or embeds.

  • SEO Optimization: Use the next/head component to add meta tags and improve SEO.


Conclusion

Building a blog with Next.js and Markdown is an efficient and scalable way to create a content-driven website. By leveraging Next.js's powerful static site generation features and Markdown’s simple yet flexible syntax, you can create a fast, SEO-friendly blog with ease.

Are you ready to start building your own blog with Next.js and Markdown? Follow the steps above and create your own blog today! Feel free to customize it with your own features and design. If you need help, check out the official documentation or reach out to the community for support.


References/Resources