
I recently added a blog to this site. Rather than reaching for a CMS, I decided to build it directly into the existing Next.js application using standard tools.
Static Markdown, No CMS
I wanted a place to share technical notes without adding another system to maintain. The constraints were concrete:
- Repo-backed Markdown: posts should live beside the rest of the site.
- Static output: each post should build into HTML during deploy.
- No admin runtime: publishing should not require a CMS, database, or editor surface.
Markdown files and static routes
I created a simple utility, blogApi.ts, that reads directly from the file system. It uses gray-matter to parse frontmatter and a remark/rehype pipeline, including rehype-pretty-code, to process the content.
Next.js makes it straightforward to turn a folder of markdown files into routes. I used generateStaticParams to tell Next.js which paths to build at compile time:
export async function generateStaticParams() {
const posts = getAllPosts(["slug"]);
return posts.map((post) => ({
slug: post.slug,
}));
}
This ensures that alexleung.ca/blog/boring-blog-architecture/ is just a static HTML file at deploy time, not a dynamic request.
The details that made it usable
The details that mattered were small, but they made the blog easier to live with:
- Typography: I used
@tailwindcss/typographybut customized it to remove the default backticks from inline code for a cleaner look. - Metadata: Each post automatically generates its own SEO tags and JSON-LD structured data.
- Syntax Highlighting: I chose
rehype-pretty-code(powered by Shiki). It uses the same TextMate grammars as VS Code, so the highlighting is close to what I am used to in the editor. It also generates inline styles, so there is no separate CSS import to manage. - Sitemap: A dynamic script crawls my posts directory to keep
sitemap.xmlup to date automatically.
The result is simple on purpose. I can write in Markdown, keep everything in the repo, and ship a blog with a rendering path that is easy to understand.
Get new posts by email
Subscribe for occasional updates when I publish something new.
Related posts
One Manifest for Responsive Images
March 4, 2026
I replaced scattered responsive-image conventions in a Next.js static site with one generated manifest for variants, dimensions, and runtime lookup.
Camping Indoors in San Francisco
April 27, 2026
My first week in San Francisco: paperwork, an unfurnished apartment, two cats, Bay Trail runs, and waiting for furniture.
Coding Agents for Inspectable Browser Tools
April 10, 2026
Building frontend-only tools for load flow, Mandelbrot zooming, optimizer behavior, and event-loop ordering gave me concrete ways to inspect older technical models.