file-based-blog-with-nextjs.md
File-Based Blogs on Next.js Static Export
How to ship a markdown blog with zero database, full SEO, and static export — the same stack powering mahdi.is-a.dev.
- Next.js
- Markdown
- Static Export
Markdown files in a content/ folder are underrated. No CMS login, no API keys, no migration scripts — just git history and a build step.
Why file-based?
For a personal tech blog, the requirements are simple:
- Version control — every edit is a commit
- Fast builds — parse at build time, serve static HTML
- Portability — move hosts without exporting a database
- Developer UX — write in VS Code, preview locally
Static export (output: 'export') on Next.js 13+ makes this especially clean. Server components read the filesystem during next build, generate HTML for every route, and you deploy plain files to any CDN.
The pipeline
A typical setup looks like this:
// lib/blog.ts — simplified
import fs from "fs";
import matter from "gray-matter";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeStringify from "rehype-stringify";
export async function getPostBySlug(slug: string) {
const raw = fs.readFileSync(`content/blog/${slug}.md`, "utf8");
const { data, content } = matter(raw);
const html = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype)
.use(rehypePrettyCode, { theme: "one-dark-pro" })
.use(rehypeStringify)
.process(content);
return { ...data, html: String(html) };
}Frontmatter holds metadata; the body stays pure markdown.
Supported markdown features
- GFM tables,
strikethrough, andinline code - Task lists with checkboxes
- GitHub alerts, footnotes1, and autolinks like https://nextjs.org
- Syntax highlighting with line numbers, filenames, and
{line}highlights
- Parse markdown at build time
- Highlight code with Shiki themes
- Client-side CMS (not needed yet)
[!TIP] Use
published: falsein frontmatter to keep drafts out of production builds.
[!WARNING] Static export means no runtime API routes — pre-generate RSS and sitemaps during
next build.
SEO without a server
Even without SSR at request time, you can ship:
- Per-page metadata via
generateMetadata sitemap.tsfor crawlers- RSS feed generated at build time into
public/feed.xml - JSON-LD Article schema on each post
The trick with static export is that dynamic route handlers don't run in production — pre-generate feeds and sitemaps during the build instead.
Frontmatter schema
Keep it minimal but expressive:
---
title: "Your post title"
description: "One sentence for OG tags and RSS"
date: "2025-05-12"
updated: "2025-05-14" # optional
tags: ["React", "Next.js"]
featured: true
published: true
---Set published: false to hide drafts from production builds without deleting files.
Takeaways
File-based blogs scale down beautifully. You won't outgrow them until you need multi-author workflows, scheduled publishing, or a non-technical editor — and by then you'll know exactly what CMS features you actually need.
Until then: write markdown, commit, deploy.
Footnotes
-
Footnotes render at the bottom of the post — great for citations without breaking reading flow. ↩
Related
Continue reading
More notes on similar topics.
A living reference for writing posts — thumbnails, code, tables, alerts, footnotes, images, audio, video, and YouTube embeds.
- Markdown
- Blog
How Ken Perlin's gradient noise creates infinite terrain, clouds, and fire — and why Simplex improved it thirty years later.
- Graphics
- Algorithms
A frontend engineer's guide to the architecture behind GPT — self-attention, positional encoding, and the encoder-decoder split.
- ML
- Transformers