How to Build a Blogging Application Like Headbanger Blogs

How to Build a Blogging Application Like Headbanger Blogs

Headbanger avatarHeadbanger
March 1, 2026
|
8 min read
Next.jsBloggingFull StackJavaScriptMDXTutorial

How to Build a Blogging Application Like Headbanger Blogs

This guide walks you through how to build a blogging platform similar to Headbanger Blogs:

  • Content lives in Markdown/MDX files inside a content/ folder
  • Next.js (App Router) handles routing, rendering, and SEO
  • A small lib/blog.js module reads files, parses frontmatter, and computes reading-time
  • The UI is built with React components and Tailwind CSS

You can follow this guide to either:

  • Rebuild this project from scratch, or
  • Understand how this repository works so you can customize it

1. Project Setup

1.1 Initialize a Next.js App Router project

Use the official Next.js starter:

npx create-next-app@latest headbanger-blogs cd headbanger-blogs

Make sure you choose:

  • App Router (recommended)
  • TypeScript or JavaScript (this project uses JavaScript for most files)
  • Tailwind CSS (or add it later)

1.2 Install required dependencies

This project relies on a few key packages:

  • gray-matter – parse frontmatter from Markdown
  • react-markdown – render Markdown as React components
  • reading-time – estimate reading time from content
  • lucide-react – icons
  • tailwindcss + plugins – styling and typography

Install 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.


2. Content-First Architecture (Markdown + Folders)

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.

2.1 Directory structure

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 advanced-javascript/ index.md
  • index.md – the main article for that topic

2.2 Frontmatter and body

Each 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.
  • The --- block is frontmatter – metadata used for the home page, cards, and SEO
  • The rest is standard Markdown, rendered by react-markdown with custom components

3. Reading and Transforming Content (lib/blog.js)

The file lib/blog.js is the heart of the content system. It handles file system operations to parse your Markdown files into structured data.

It:

  • Reads directories inside content/ using Node's fs
  • Parses each index.md (and index.mdx) with gray-matter
  • Computes readTime using reading-time

Here is how getAllBlogPosts() is implemented:

export function getAllBlogPosts() { const blogDirs = fs.readdirSync(contentDirectory, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); return blogDirs.map(slug => { let indexPath = path.join(contentDirectory, slug, 'index.md'); if (!fs.existsSync(indexPath)) { indexPath = path.join(contentDirectory, slug, 'index.mdx'); } const fileContent = fs.readFileSync(indexPath, 'utf8'); const { data, content } = matter(fileContent); // Automatic Cover Image Resolution // Resolves cover image from frontmatter or falls back to finding a 'cover.*' file in the directory. return { slug, title: data.title || slug, description: data.description || '', date: data.date || new Date().toISOString(), author: data.author || 'Anonymous', tags: data.tags || [], readTime: readingTime(content).text, content, coverImage: coverImagePath, }; }).sort((a, b) => new Date(b.date) - new Date(a.date)); }

This module also exports helpful navigation functions like getPreviousPost, getNextPost, and getSuggestedPosts, ensuring that creating related-post sections later in the UI is a breeze.


4. Routing and Pages (App Router)

This project uses the App Router (app/ directory) to define pages and server-side render the blog files.

4.1 Home page – list of all posts

File: app/page.js

  • Calls getAllBlogPosts() on the server.
  • Passes the result into a client component HomeClient which handles search, filter by tag, and renders cards via components/BlogCard.js. This keeps data loading on the server but interactivity on the client.

4.2 Dynamic route for main posts

File: app/blog/[slug]/page.js Route: /blog/:slug

This server component is responsible for retrieving the post via its slug, formatting its data, and using next-mdx-remote/rsc to render the content alongside an immersive UI.

import { getBlogPost, getSuggestedPosts, getPreviousPost, getNextPost } from '@/lib/blog'; import { MDXRemote } from 'next-mdx-remote/rsc'; import MDXComponents from '@/components/MDXComponents'; import PostSidebar from '@/components/LeftSidebar'; export default async function BlogPage({ params }) { const { slug } = await params; const post = getBlogPost(slug); if (!post) return notFound(); return ( <div className="min-h-screen bg-background"> {/* Immersive Header Image & Meta Info */} <div className="w-full mx-auto px-4 py-8"> <Image src={post.coverImage} alt={post.title} /> <h1>{post.title}</h1> </div> {/* Blog Body & Sidebars */} <div className='flex gap-8 md:mx-auto justify-center'> <div className='md:w-80'> {/* Table of Contents Generation */} <PostSidebar content={post.content} title={post.title} /> </div> <div className='md:w-[50vw]'> <article id="mdx-content"> <MDXRemote source={post.content} components={MDXComponents} /> </article> </div> </div> </div> ); }

It beautifully couples the content generation with Next.js specific SSR mechanisms like generateStaticParams() to pre-render the paths.


5. Rendering Markdown Nicely (MDXComponents.js)

Markdown is rendered via react-markdown (or next-mdx-remote) and a custom MDXComponents map defined in components/MDXComponents.js.

This file is crucial for mapping standard markdown syntax (like # heading or ```code```) into visually striking, styled React components using Tailwind classes.

import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'; const components = { h1: ({ children }) => ( <h1 className="text-4xl font-normal font-serif text-white/90 mb-6 mt-8 leading-tight border-b border-border pb-3 tracking-wide"> {children} </h1> ), p: ({ children }) => ( <p className="text-base md:text-lg text-white/80 leading-[1.8] tracking-wide mb-6 font-sans"> {children} </p> ), blockquote: ({ children }) => ( <blockquote className="border-l-4 border-primary/50 text-white/80 pl-6 py-4 mb-8 bg-primary/5 text-lg italic rounded-r-lg"> {children} </blockquote> ), code: ({ children, className, inline, ...props }) => { // Uses SyntaxHighlighter for code blocks for rich syntax highlighting const match = /language-(\w+)/.exec(className || ''); if (inline || !match) { return <code className="bg-muted px-1.5 py-0.5 rounded-md">{children}</code>; } return ( <SyntaxHighlighter style={vscDarkPlus} language={match[1]} PreTag="div"> {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ); }, }; export default components;

By keeping this separated, you achieve full control over the typography and look of the entire platform without muddying the markdown files.


6. Sidebar and Navigation Features

6.1 Table of contents sidebar

components/LeftSidebar.js (exported as PostSidebar) dynamically builds a list of headings by:

  1. Scanning the rendered document for h1–h6 tags
  2. Generating IDs for headings if they don't exist
  3. Tracking which heading is currently visible using IntersectionObserver
  4. Rendering a clickable list that smoothly scrolls to each section

This gives you an automatic Table of Contents for long articles—no extra work in the content files.

6.2 Suggested posts navigation

components/RightSidebar.js (exported as PostNavigation) renders:

  • The main article title and total reading time
  • A "Suggested Posts" section with other relevant articles

These components turn a simple blog into a mini learning platform with structured navigation.


7. Styling with Tailwind CSS

The UI is built using Tailwind CSS classes:

  • Layout: flex, grid, responsive widths (w-full, md:w-[50vw], etc.)
  • Colors: bg-background, text-muted-foreground, etc.
  • Typography: utility classes for headings, spacing, and text colors

To customize the look:

  1. Update the global styles in app/globals.css
  2. Adjust Tailwind config (colors, fonts, etc.)
  3. Tweak component classes in components/ for specific layouts

8. Adding a New Post (Your Workflow)

Once the system is set up, adding a new blog topic is simple:

  1. Create a new folder inside content/ using a URL-friendly slug, e.g.:

    content/ my-new-topic/ index.md
  2. 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.
  3. Commit your changes and deploy – the new post automatically:

    • Appears on the home page
    • Gets its own /blog/my-new-topic route
    • Benefits from all navigation, sidebars, and styling

9. Deployment

You can deploy this app to any Next.js-compatible platform. Common options:

  • Vercel – first-class support for Next.js, zero-config deployments
  • Netlify – good support for static/SSR Next.js apps
  • Self-hosted – Node server or Docker container

Typical workflow with Vercel:

  1. Push your project to GitHub
  2. Connect the repository in Vercel
  3. Vercel auto-detects Next.js and builds the app
  4. Every push to main triggers a new deployment

10. How to Customize This Blog for Your Own Use

Once you understand the structure, you can:

  • Change the branding (logo, site name, colors)
  • Add new layout components (e.g., a top navigation bar)
  • Extend frontmatter with new fields (e.g., category, difficulty, series)
  • Integrate analytics or comments
  • Swap file-based content for a headless CMS later, keeping the same rendering components

Because the content system and UI are decoupled, you can iterate on the design without touching the Markdown files—and vice versa.


11. Summary

To build a blogging app like this one, you mainly need to:

  1. Set up Next.js + Tailwind CSS
  2. Design a content-first structure in content/ with index.md (or index.mdx)
  3. Write helper functions in lib/blog.js to read files and return structured data
  4. Create dynamic routes under app/blog/[slug]
  5. Render Markdown with custom components via react-markdown + MDXComponents
  6. Enhance UX with navigation, sidebars, reading time, and suggested posts

This repository already includes all of these pieces—use this guide as a map to understand, extend, and build your own version of Headbanger Blogs.