How I Built This Personal Blog (Next.js + Markdown + Tailwind)
This site is a lightweight, static personal blog built with Next.js, Tailwind CSS, and Markdown. It’s optimized for fast loads, simple authoring, and an easy reading experience with light/dark themes.
Technology Stack
- Next.js 16 (React 19) for the app framework and static generation (SSG)
- Tailwind CSS 2 for utility‑first styling and theme tokens
- Markdown for content, parsed by
gray-matterand rendered withremark+remark-html date-fnsfor date formatting- Vercel Analytics for privacy‑friendly traffic insights
Project Structure
personalblog/
├─ components/
│ ├─ layout.js # Site chrome (header, avatar, footer), wraps pages
│ └─ Toggle.js # Light/Dark theme switch
├─ context/
│ └─ themeContext.js # Theme provider + SSR-safe hydration
├─ lib/
│ └─ posts.js # Markdown loading, sorting, and HTML conversion
├─ pages/
│ ├─ index.js # Home page listing all posts
│ └─ posts/[id].js # Dynamic routes for individual posts
├─ posts/ # Markdown posts with frontmatter
├─ public/ # Images, icons (e.g., GitHub/LinkedIn SVGs)
├─ styles/ # Global/util styles (Tailwind consumed)
├─ tailwind.config.js # Tailwind + theme tokens wired to CSS variables
└─ next.config.js # Next.js config (kept minimal)
The repo started from the official Next.js “Learn” starter (see README.md), then added Tailwind, a theme system, Markdown parsing, and small UI refinements.
Content Pipeline (Markdown → HTML)
Posts live in posts/ as .md files with YAML frontmatter. The loader in lib/posts.js:
- Reads all files in
posts/ - Uses
gray-matterto extract frontmatter (title,date, etc.) - Converts markdown content to HTML via
remark+remark-html - Sorts posts by
datedescending for the homepage
Core utilities (excerpt):
// lib/posts.js
import matter from 'gray-matter'
import remark from 'remark'
import html from 'remark-html'
export function getSortedPostData() {
// read files, parse frontmatter, sort by date desc
}
export async function getPostData(id) {
const matterResult = matter(fileContents)
const processedContent = await remark().use(html).process(matterResult.content)
return { id, contentHtml: processedContent.toString(), ...matterResult.data }
}
This keeps authoring simple: write Markdown with a short frontmatter block, commit, and the site rebuilds with your new post.
Pages and Routing (SSG)
pages/index.jsusesgetStaticPropsto callgetSortedPostData()and render a grid of posts.pages/posts/[id].jsimplements dynamic routes withgetStaticPaths+getStaticProps, rendering the converted HTML withdangerouslySetInnerHTML.
This approach fully statically generates all posts at build time for great performance and stability.
UI, Layout, and Theming
- Layout lives in
components/layout.jsand wraps every page with:- A glass-style sticky header with GitHub/LinkedIn icons
- An avatar + name banner
- A simple footer
- The theme system uses Tailwind’s
darkMode: 'class'plus a small context provider (context/themeContext.js).- Default is
darkduring SSR to avoid mismatches. - On the client, the stored preference (or system preference) is applied.
components/Toggle.jsswitches between light/dark.
- Default is
Tailwind tokens (e.g., bg-primary, text-secondary) are wired to CSS variables in tailwind.config.js, making theming clean and scalable without duplicating utility classes.
Analytics
@vercel/analytics is initialized on the homepage to capture high‑level usage without manual setup.
Local Development
npm install
npm run dev
# visit http://localhost:3000
To create a new post, add a Markdown file in posts/:
---
title: 'Your Post Title'
date: '2025-12-07'
---
Your content here in Markdown.
Commit and push; the post appears on the homepage after the next build.
Notable Implementation Details
- Sorting by date ensures the newest post appears first on the homepage.
- Dates are rendered via a small
Datecomponent (usesdate-fns). - The theme toggle avoids hydration issues by rendering a placeholder until the client mounts.
- The markdown converter is intentionally minimal (
remark-html) to keep bundles small.
That’s it — a fast, minimal, and pleasant writing setup that stays out of the way.
