Curriculum/Building a Blog with Markdown
Last Updated 15 min read

Building a Blog with Markdown

Learn how to treat content as data using Next.js, Markdown, and Dynamic Routes.

What we will build

We are going to build a scalable blog engine where you write your posts in simple markdown files, and Next.js automatically turns them into beautiful, styled web pages. We will cover dynamic routing, reading files from your system, and rendering them safely.

1. The Problem: Hardcoded Pages

Without dynamic content, if you wanted to write 50 blog posts, you would have to manually create 50 different folders and 50 different page.tsx files.

app/blog/post-1/page.tsx

app/blog/post-2/page.tsx

app/blog/post-3/page.tsx

... and so on.

This is unmaintainable. If you wanted to change the title font size, you'd have to edit 50 files.

The solution is to separate Content (what you write) from Structure (how it looks). We write content in .md files and use one single template to display all of them.

2. Setting up Markdown

First, we need a few packages to help Next.js read and render these files.

pnpm add remark remark-html remark-gfm gray-matter @tailwindcss/typography
  • remark + remark-html: Convert markdown to HTML on the server.
  • remark-gfm: Adds support for tables, strikethrough, and other GitHub Flavored Markdown.
  • gray-matter: Lets us add metadata to the top of our files (like Title, Date, Author) called "frontmatter".
  • @tailwindcss/typography: A plugin that automatically styles your markdown (headers, bold text, lists) so you don't have to write CSS for every paragraph.

3. Architecture

We need a specific place to store our raw blog posts. We don't put them inside the app/ folder because they aren't pages themselves; they are data.

We create a content/ folder at the root of our project.

app
content
hello-world.md
nextjs-guide.md
why-i-code.md

Inside one of these files (hello-world.md), it looks like this:

---
title: "Hello World"
date: "2024-03-15"
description: "My first post on the new site."
---

# Welcome to my blog

This is a paragraph written in standard markdown.

The part between the --- dashes is the Frontmatter. That is the data we will read to display the title and date on your blog index.

4. Dynamic Routing

Now, how do we create one page that renders any of those files? We use a Dynamic Segment.

In Next.js, if you put square brackets around a folder name, it acts as a variable. We will create app/blog/[slug]/page.tsx.

app
blog
[slug]
page.tsx
portfolio.com/blog/hello-world

When a user visits /blog/hello-world, Next.js grabs "hello-world", puts it into a variable called slug, and passes it to our page. We then tell our code: "Go look in the content folder for a file named hello-world.md."

5. The Logic

Here is the simplified logic of how we read that file. We use Node.js's built-in file system (fs) module.

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

const root = process.cwd()

export function getPost(slug: string) {
  const filePath = path.join(root, 'content', `${slug}.md`)
  const fileContent = fs.readFileSync(filePath, 'utf8')

  // separate metadata (frontmatter) from the actual content
  const { data, content } = matter(fileContent)

  return { metadata: data, content }
}

This structure allows your blog to grow infinitely. You just add .md files to the folder, and they automatically exist as pages on your site.

Prompt Section

Implementing a blog system involves a lot of boilerplate code (reading files, parsing dates, setting up types). Use this prompt to have Cursor build the entire engine for you in one go.

Copy this into Cursor
# Role You are a Senior Next.js Architect. You are an expert in Node.js `fs` (file system), Markdown (remark), and Server React Components. Objective Create a fully functional, type-safe blog engine using Markdown files as the data source. Tech Stack Framework: Next.js 16 (App Router) Content: Markdown (via remark + remark-html) Metadata: Gray-matter Styling: Tailwind CSS + Typography Plugin Instructions Step 1: Install Dependencies Run the command to install necessary packages: pnpm add remark remark-html remark-gfm gray-matter @tailwindcss/typography Step 2: Configure Tailwind Update tailwind.config.ts to include the typography plugin. This is critical for making the markdown look good (headings, paragraphs, lists) without manual styling. Step 3: Create Content Directory Create a folder named content in the root directory. Create two sample Markdown files inside it: hello-world.md: Include frontmatter (title, date, description) and some sample markdown content. architecture-guide.md: Include a code block and a list in the markdown. Step 4: Create the API Utility Create lib/api.ts. This file should export: getPost(slug): Reads a specific file, parses frontmatter using gray-matter, and returns the data and raw content. getAllPosts(): Reads the content directory, returns an array of all posts sorted by date (newest first). Constraint: Ensure strict TypeScript typing for the Frontmatter object (title, date, description). Step 5: Create markdownToHtml Create lib/markdownToHtml.ts that uses remark, remark-html, and remark-gfm to convert markdown strings to HTML. Step 6: Create the Blog Index Create app/blog/page.tsx. Fetch all posts using getAllPosts(). Display them in a grid using shadcn Cards (or clean Tailwind). Each card must link to /blog/[slug]. Step 7: Create the Dynamic Post Page Create app/blog/[slug]/page.tsx. : Use this function to statically generate routes for every file in at build time.

Armstrong Academy
Complete Module →