Low-code tools like WordPress simplify the blog creation process. You can use a pre-made theme and start writing blog posts in a matter of hours. If you want more control over your code and have some time on your hands, it's better to build your blog from scratch. You can even use a framework like Next.js to simplify the process.

Learn how to build a simple Next.js blog that renders markdown posts.

Creating a Next.js Project

Next.js is a React framework that simplifies how you build applications. It provides many tools and configurations out of the box, allowing you to start writing code immediately after installing it.

The simplest way to get started with Next.js is by running the create-next-app command in a terminal:

        npx create-next-app markdown-blog

This command creates a Next.js project containing all the necessary files to start.

First things first, clean up the index.js file to look like this:

        import Head from 'next/head'
import styles from '../styles/Home.module.css'
 
export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
    </div>
  )
}

Create Markdown Blog Posts

The blog will render markdown files stored locally in the project folder. So, create a new folder at the root called content to store the files. In this folder, create a new file called create-active-link-nextjs.md and add the following:

        ---
title: How To create an active link in Nextjs
description: Customizing active links using useRouter()
isPublished: true
publishedDate: 2022/07/22
tags:
  - next
---

## Main content

The name of the markdown file will be part of the post URL, so make sure it's a good one. Also, note the contents between the dashes. This is the metadata of the post and is called the front matter.

Parsing Markdown Files

For each blog post, you must parse the markdown content and the front matter. For Markdown, use react-markdown and for the front matter data, use gray-matter.

React-markdown is a React component built on remark that safely converts markdown into HTML. The gray-matter library parses front matter and converts YAML into an object.

Execute the following command in the terminal to install react-markdown and gray-matter.

        npm install react-markdown gray-matter

In a new folder called utils, create a new file called md.js. You will create helper functions that return blog post content in this file.

Get All Published Posts

In md.js, add the following code to return all the posts in the content folder.

        import fs from "fs";
import path from "path";
import matter from "gray-matter";
 
export const getPath = (folder:string) => {
    return path.join(process.cwd(), `/${folder}`); // Get full path
}
 
export const getFileContent = (filename:string, folder:string) => {
    const POSTS_PATH = getPath(folder)
    return fs.readFileSync(path.join(POSTS_PATH, filename), "utf8");
};
 
export const getAllPosts = (folder:string) => {
  const POSTS_PATH = getPath(folder)
 
  return fs
    .readdirSync(POSTS_PATH) // get files in directory
    .filter((path) => /\\.md?$/.test(path)) // only .md files
    .map((fileName) => { // map over each file
      const source = getFileContent(fileName, folder); // retrieve the file contents
      const slug = fileName.replace(/\\.md?$/, ""); // get the slug from the filename
      const { data } = matter(source); // extract frontmatter
      return {
        frontmatter: data,
        slug: slug,
      };
    });
};

In the getAllPosts() function:

  • Get the full path to the content folder using the path module.
  • Get the files in the content folder using the fs.readdirSync() method.
  • Filter the files to only include files with the .md extension.
  • Retrieve the contents of each file, including the front matter using the map method.
  • Return an array containing the front matter and the slug (the file name without the .md extension) of each file.

To get only the published posts, you can filter all the posts and only return those whose isPublished key in the front matter is set to true.

        export const getAllPublished = (folder:string) => {
    const posts = getAllPosts(folder)
 
    const published = posts.filter((post) => {
      return post.frontmatter.isPublished === true
    })
 
    return published
  }

In md.js, add the getSinglePost() function to retrieve the contents of a single post.

        export const getSinglePost = (slug:string, folder:string) => {
  const source = getFileContent(`${slug}.md`, folder);
  const { data: frontmatter, content } = matter(source);
 
  return {
    frontmatter,
    content,
  };
};

This function calls the getFileContent() function to get the contents of each file. Then using the gray-matter package, the function retrieves the front matter and the markdown content.

Display All the Blog Posts

Next.js provides different rendering options, one of them being static generation. Static generation is a type of pre-rendering where Next.js generates all the HTML pages during build time. You use it to create fast static pages.

Check out the official Nextjs documentation for more information on rendering.

Next.js will pre-render a page at build time using the props returned by the getStaticProps function. In this case, the props will be an array of published posts.

        export const getStaticProps = async () => {
  const posts = getAllPublished("posts");
 
  return {
    props: { posts },
  };
};

Modify the index.js file to display a list of blog posts.

        import Head from "next/head";
import Link from "next/link";
import { getAllPublished } from "../utils/md";
 
function Home({ posts }) {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div>
        {posts.map((post) => (
          <article key={post.slug}>
            <p>[ {post.frontmatter.tags.join(", ")} ]</p>
            <Link href={`posts/${post.slug}`}>
              <a>{post.frontmatter.title}</a>
            </Link>{" "}
            <p>{post.frontmatter.description}</p>
          </article>
        ))}
      </div>
    </div>
  );
}
 
export const getStaticProps = async () => {
  const posts = getAllPublished("content");
 
  return {
    props: { posts },
  };
};
 
export default Home;

The Home component uses the posts returned by getStaticProps. It iterates over them using the map function, and for each post, it displays a title, a link to the full post, and a description.

Display a Blog Post

As mentioned, the posts' filenames will be used as URL paths. These paths are also dynamic, so you need to generate them during build time. Next.js allows you to do this using the getStaticPaths() function.

For example, in this code, the paths are generated from the names of the markdown files.

        export const getStaticPaths = async () => {
  const paths = getAllPublished("posts").map(({ slug }) => ({ params: { slug } }));
 
  return {
    paths,
    fallback: false,
  };
};

Note you are using the posts data returned by the getAllPublished() helper function you created before.

You are also setting fallback to false, which returns a 404 error for paths that don't exist.

getStaticPaths() is usually used with getStaticProps() which retrieves the contents of each post based on the params.

        export const getStaticProps = async ({ params }) => {
  const post = await getSinglePost(params.slug, "posts");
 
  return {
    props: { ...post },
  };
};

To render the markdown to HTML, use react-markdown.

        import ReactMarkdown from 'react-markdown'
import { getAllPosts, getSinglePost } from "../../utils/md";
 
const Post = ({ content, frontmatter }) => {
  return (
    <div>
        <p>{frontmatter.tags.join(', ')}</p>
        <h2>{frontmatter.title}</h2>
        <span>{frontmatter.publishedDate}</span>
        <ReactMarkdown>{content}</ReactMarkdown>
    </div>
  );
};

This component will render the contents of each blog post and its corresponding URL.

If creating a developer blog, you can add syntax highlighting capability for each component.

Styling the Next.js Markdown Blog

So far, you have created a Next.js markdown blog that displays a list of blog posts and renders the contents of that post. To make the blog look nicer, you should add CSS styles.

Next.js has good CSS support, and you can choose to use CSS-in-JS libraries like styled components. If you prefer to separate CSS from JS, you can use CSS modules.