
This guide walks you through how to build a blogging platform similar to Headbanger Blogs:
content/ folderlib/blog.js module reads files, parses frontmatter, and computes reading-timeYou can follow this guide to either:
Use the official Next.js starter:
npx create-next-app@latest headbanger-blogs
cd headbanger-blogs
Make sure you choose:
This project relies on a few key packages:
gray-matter – parse frontmatter from Markdownreact-markdown – render Markdown as React componentsreading-time – estimate reading time from contentlucide-react – iconstailwindcss + plugins – styling and typographyInstall them:
npm install gray-matter react-markdown reading-time lucide-react
If you didn't enable Tailwind in the Next.js wizard, also install and set it up following the Tailwind docs.
Instead of storing posts in a database, this blog uses files on disk. This keeps things simple and works great for personal blogs and documentation sites.
At the root of the project, create a content/ folder. Each top-level folder inside content/ represents one main blog post / topic:
content/
getting-started-react/
index.md
hooks-and-state/
subpost.mdx
advanced-javascript/
index.md
async-programming/
subpost.mdx
index.md – the main article for that topicsubpost.mdx file – deeper sub-articles under the main topicEach index.md looks like this:
---
cover: "./cover.png"
title: "Getting Started with React: A Complete Guide"
description: "Short description for the home page and SEO."
date: "2024-01-15"
author: "Headbanger"
tags: ["React", "JavaScript", "Frontend"]
---
# Main Title
Your Markdown content starts here.
--- block is frontmatter – metadata used for the home page, cards, and SEOreact-markdown with custom componentsSubposts (subpost.mdx) use similar frontmatter:
---
title: "Async Programming in JavaScript"
description: "Learn how to use callbacks, promises, and async/await."
---
# Async Programming in JavaScript
Content...
lib/blog.js)The file lib/blog.js is the heart of the content system. It:
content/index.md with gray-matterreadTime using reading-timecoverImage using imageUtilssubpost.mdx)The important exported functions are:
getAllBlogPosts() – returns all top-level posts with metadata + contentgetBlogPost(slug) – returns one post by folder namegetSubPost(blogSlug, subpostSlug) – returns a specific subpostgetPreviousPost / getNextPost – for navigationgetSuggestedPosts – returns a small, shuffled list of other postsgetAllBlogPosts() works (conceptually)content/ using Node's fs and pathindex.mdgray-matter to split frontmatter and contentreadingTime(content).textcoverImagegetSubPosts(slug)date (newest first)You don't usually need to change this logic unless you:
This project uses the App Router (app/ directory) to define pages.
File: app/page.js
getAllBlogPosts()HomeClientHomeClient handles search, filter by tag, and renders cards via BlogCardThis keeps data loading on the server but interactivity on the client.
File: app/blog/[slug]/page.js
Route: /blog/:slug
Responsibilities:
getBlogPost(slug) to fetch the post datagetPreviousPost / getNextPost and getSuggestedPosts to build navigation<ReactMarkdown components={MDXComponents}>
{post.content}
</ReactMarkdown>
PostSidebar to build a dynamic table of contents from headingsPostNavigation to show subposts and suggested postsFolder structure:
app/
blog/
[slug]/
page.js // main post
[subpost]/
page.js // subpost
Route: /blog/:slug/:subpost
The subpost page:
getSubPost(slug, subpostSlug) from lib/blog.jsReactMarkdown + MDXComponentsMDXComponents.js)Markdown is rendered via react-markdown and a custom MDXComponents map.
components/MDXComponents.js customizes the look of:
h1–h6)p)ul, ol, li)a)pre, code)This gives you full control over how Markdown appears, while keeping the content plain-text and easy to edit.
If you want to change typography or spacing, you only edit MDXComponents.js, not each post.
components/LeftSidebar.js (exported as PostSidebar) dynamically builds a list of headings by:
h1–h6 tagsIntersectionObserverThis gives you an automatic Table of Contents for long articles—no extra work in the content files.
components/RightSidebar.js (exported as PostNavigation) renders:
These components turn a simple blog into a mini learning platform with structured navigation.
The UI is built using Tailwind CSS classes:
flex, grid, responsive widths (w-full, md:w-[50vw], etc.)bg-background, text-muted-foreground, etc.To customize the look:
app/globals.csscomponents/ for specific layoutsOnce the system is set up, adding a new blog topic is simple:
Create a new folder inside content/ using a URL-friendly slug, e.g.:
content/
my-new-topic/
index.md
Add frontmatter and content to index.md:
---
cover: "./cover.png"
title: "My New Topic"
description: "Short description for the home page."
date: "2026-03-01"
author: "Headbanger"
tags: ["Tutorial", "JavaScript"]
---
# My New Topic
Your Markdown content here.
(Optional) Add subposts:
content/
my-new-topic/
index.md
part-1/
subpost.mdx
part-2/
subpost.mdx
Commit your changes and deploy – the new post automatically:
/blog/my-new-topic routeYou can deploy this app to any Next.js-compatible platform. Common options:
Typical workflow with Vercel:
main triggers a new deploymentOnce you understand the structure, you can:
category, difficulty, series)Because the content system and UI are decoupled, you can iterate on the design without touching the Markdown files—and vice versa.
To build a blogging app like this one, you mainly need to:
content/ with index.md + subpost.mdxlib/blog.js to read files and return structured dataapp/blog/[slug] and app/blog/[slug]/[subpost]react-markdown + MDXComponentsThis repository already includes all of these pieces—use this guide as a map to understand, extend, and build your own version of Headbanger Blogs.