Build a Simple Next.js Interactive Web-Based Markdown Blog

Written by

in

In the ever-evolving world of web development, creating a personal blog is a rite of passage for many developers. It’s a fantastic way to share your knowledge, showcase your projects, and build an online presence. While platforms like WordPress and Medium offer ease of use, they often come with limitations in terms of customization and control. This tutorial will guide you through building your own Markdown-based blog using Next.js, a powerful React framework, giving you full control over your content and design.

Why Build a Markdown Blog?

Markdown is a lightweight markup language that allows you to format text with simple syntax. It’s easy to learn, efficient, and makes writing content a breeze. By using Markdown, you can focus on the content rather than wrestling with complex HTML editors. Next.js, on the other hand, provides a robust framework for building modern web applications with features like server-side rendering, static site generation, and optimized performance. Combining these two technologies results in a fast, SEO-friendly, and highly customizable blog.

What You’ll Learn

In this tutorial, you’ll learn how to:

  • Set up a Next.js project.
  • Structure your blog’s file system.
  • Write and parse Markdown files.
  • Display blog posts dynamically.
  • Add basic styling.
  • Deploy your blog.

Prerequisites

Before you begin, make sure you have the following:

  • Node.js and npm (or yarn) installed on your machine.
  • A basic understanding of HTML, CSS, and JavaScript.
  • A code editor (like VS Code)

Step-by-Step Guide

1. Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app my-markdown-blog

Replace my-markdown-blog with your desired project name. This command will create a new directory with the necessary files and dependencies. Once the installation is complete, navigate into your project directory:

cd my-markdown-blog

Now, start the development server:

npm run dev

Open your browser and go to http://localhost:3000 to see the default Next.js welcome page. You’ve successfully set up your Next.js project!

2. Project Structure and Markdown Files

Create a new directory called posts in the root of your project. This is where we’ll store our Markdown files. Inside the posts directory, create a sample Markdown file, such as first-post.md. Here’s an example of what your first-post.md file might look like:


---
title: "My First Blog Post"
date: "2024-01-20"
---

# Hello, World!

This is my first blog post written in Markdown.

It's great!

Notice the metadata at the top, enclosed in triple dashes (---). This is called a frontmatter, and it contains information about the post, such as the title and date. We’ll use this information later to display the post’s details.

3. Installing Dependencies

We’ll need a few packages to help us parse Markdown and work with the file system. Install these using npm or yarn:

npm install gray-matter remark remark-html

or

yarn add gray-matter remark remark-html
  • gray-matter: Parses the frontmatter from our Markdown files.
  • remark: A Markdown processor.
  • remark-html: Converts Markdown to HTML.

4. Creating a Utility Function to Read and Parse Posts

Create a new file called lib/posts.js in your project. This file will contain functions to read and parse our Markdown files.


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

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

export async function getSortedPostsData() {
  // Get file names under /posts
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = await Promise.all(
    fileNames.map(async (fileName) => {
      // Remove ".md" from file name to get id
      const id = fileName.replace(/.md$/, '');

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

      // Use gray-matter to parse the post metadata section
      const matterResult = matter(fileContents);

      // Use remark to convert markdown into HTML
      const processedContent = await remark()
        .use(html)
        .process(matterResult.content);
      const content = processedContent.toString();

      // Combine the data with the id
      return {
        id,
        content,
        ...matterResult.data,
      };
    })
  );
  // Sort posts by date
  return allPostsData.sort(({ date: a }, { date: b }) => {
    if (a <b> b) {
      return -1;
    }
    return 0;
  });
}

export async function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory);

  return fileNames.map((fileName) => {
    return {
      params: {
        id: fileName.replace(/.md$/, ''),
      },
    };
  });
}

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

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents);

  // Use remark to convert markdown into HTML
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content);
  const content = processedContent.toString();

  // Combine the data with the id
  return {
    id,
    content,
    ...matterResult.data,
  };
}

Let’s break down what this code does:

  • It imports necessary modules for file system operations, Markdown parsing, and HTML conversion.
  • getSortedPostsData: Reads all Markdown files, parses their frontmatter and content, converts the Markdown to HTML, and returns an array of post objects sorted by date.
  • getAllPostIds: Gets all the possible paths for our blog posts, which is needed for dynamic routes.
  • getPostData: Reads a single Markdown file based on its ID, parses its frontmatter and content, converts the Markdown to HTML, and returns the post object.

5. Displaying a List of Posts

Now, let’s modify the pages/index.js file to display a list of blog posts. Replace the existing content with the following:


import Head from 'next/head';
import Link from 'next/link';
import { getSortedPostsData } from '../lib/posts';

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

export default function Home({ allPostsData }) {
  return (
    <div>
      
        <title>My Markdown Blog</title>
        
      

      <h1>Welcome to My Blog</h1>
      <ul>
        {allPostsData.map(({ id, title, date }) => (
          <li>
            
              <a>{title}</a>
            
            <br />
            <small>{date}</small>
          </li>
        ))}
      </ul>
    </div>
  );
}

Here’s what’s happening:

  • We import Head and Link from Next.js, and the getSortedPostsData function from lib/posts.js.
  • getStaticProps is a Next.js function that fetches data at build time. We use it to get the sorted posts data and pass it as props to our component.
  • We map over the allPostsData array and create a list item for each post. Each list item includes a link to the individual post page (which we’ll create next).

6. Creating Dynamic Post Pages

Next.js makes it easy to create dynamic routes. We’ll create a new file named pages/posts/[id].js. The [id] part indicates a dynamic route parameter.


import Head from 'next/head';
import { getAllPostIds, getPostData } from '../../lib/posts';

export async function getStaticPaths() {
  const paths = await getAllPostIds();
  return {
    paths,
    fallback: false,
  };
}

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

export default function Post({ postData }) {
  return (
    <div>
      
        <title>{postData.title}</title>
      
      <h1>{postData.title}</h1>
      <div />
      <p>Published: {postData.date}</p>
    </div>
  );
}

Let’s break down this code:

  • We import getAllPostIds and getPostData from lib/posts.js.
  • getStaticPaths is a Next.js function that specifies which paths should be pre-rendered at build time. It calls getAllPostIds to get all the possible paths for our blog posts. fallback: false means that any paths not returned by getStaticPaths will result in a 404.
  • getStaticProps fetches the data for a specific post based on the id parameter from the URL.
  • The component displays the post title and content. We use dangerouslySetInnerHTML to render the HTML generated from the Markdown.

7. Adding Basic Styling

Let’s add some basic styling to make our blog look a bit nicer. Create a file called styles/global.css in your project, and add the following CSS:


body {
  font-family: sans-serif;
  margin: 20px;
}

h1, h2, h3, h4, h5, h6 {
  margin-bottom: 0.5rem;
}

a {
  color: #0070f3;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

.post-content {
  margin-top: 1rem;
}

Then, import this CSS file in your pages/_app.js file (create this file if you don’t have it):


import '../styles/global.css';

function MyApp({ Component, pageProps }) {
  return ;
}

export default MyApp;

This will apply the basic styling to your entire blog.

8. Deploying Your Blog

Now that your blog is functional, it’s time to deploy it! There are many options for deploying a Next.js application. A popular choice is Vercel, which is the platform created by the same team that created Next.js. It’s very easy to deploy to Vercel:

  • Create a free account on Vercel.
  • Connect your GitHub, GitLab, or Bitbucket repository.
  • Vercel will automatically detect that it’s a Next.js project and build and deploy it.
  • You’ll get a unique URL where your blog will be live.

Alternatively, you can deploy to Netlify, AWS, or any other hosting provider that supports Node.js applications.

Common Mistakes and How to Fix Them

  • Incorrect File Paths: Double-check your file paths, especially when importing modules and reading Markdown files. Make sure the paths are relative to the current file.
  • Missing Dependencies: Ensure you’ve installed all the required dependencies (gray-matter, remark, remark-html).
  • Frontmatter Errors: Make sure your frontmatter is correctly formatted with the correct keys (e.g., title, date). Also, ensure the date format is consistent.
  • Incorrect HTML Rendering: Make sure you’re using dangerouslySetInnerHTML to render the HTML content generated from Markdown within the post page. This is a potential security risk if you’re not careful about the content you’re displaying, but it’s necessary for rendering the Markdown-to-HTML conversion.
  • Build Errors: If you encounter build errors, carefully read the error messages. They often provide clues about missing dependencies, incorrect file paths, or syntax errors. Try running npm run build to check for errors before deploying.

Key Takeaways

  • Next.js provides a powerful framework for building modern web applications, including blogs.
  • Markdown is an efficient and easy-to-learn markup language for writing content.
  • Combining Next.js and Markdown results in a fast, SEO-friendly, and highly customizable blog.
  • Dynamic routes in Next.js make it easy to create individual pages for each blog post.
  • Deployment to platforms like Vercel is straightforward.

FAQ

Q: Can I add images to my Markdown posts?

A: Yes, you can add images using standard Markdown syntax: ![alt text](image-url). Make sure the image URL is correct.

Q: How do I add a table of contents?

A: You can use a Markdown extension or a JavaScript library to automatically generate a table of contents based on your headings (h2, h3, etc.). There are several remark plugins available that can help with this.

Q: How can I add a search function to my blog?

A: You can implement a search function by either:

  • Using client-side JavaScript to filter the posts based on user input.
  • Integrating a search service like Algolia or MeiliSearch.

Q: How do I handle different date formats?

A: You can use a library like date-fns or moment.js to format the dates in your posts consistently. Parse the date from your frontmatter and then format it in your components.

Q: How do I add tags or categories to my posts?

A: You can add tags or categories to your frontmatter (e.g., tags: ["nextjs", "tutorial"]). Then, you can filter and display posts based on these tags or categories in your blog’s UI.

Building your own Markdown blog with Next.js is a rewarding experience. It gives you complete control over your content, design, and user experience. While this tutorial provides a solid foundation, there’s always more you can do. Experiment with different Markdown features, explore advanced styling techniques, and integrate other features like comments and social sharing. The beauty of this approach lies in its flexibility and scalability; you can tailor your blog to perfectly match your needs and preferences, creating a unique and engaging platform for your content.