There is a familiar failure mode when building a personal site: the publishing system becomes more interesting than the publishing. A database appears. Then authentication. Then a dashboard, an editor, preview states, and a deployment story for all of it.
Meanwhile, the first article remains unwritten.
This site starts from a smaller premise: a blog should be a folder of files. If I can create a Markdown document, run one command, and get durable HTML, the system is doing its job.
Three constraints
I wanted the setup to satisfy three constraints from day one:
- Writing must work offline. A post is a plain-text file that can be edited in any tool and tracked in Git.
- Reading must be boringly fast. Article pages ship as static HTML and CSS. There is no client-side framework standing between the URL and the words.
- Discussion should not become a product feature. Hacker News already has identity, threading, moderation, and a technically curious audience.
Those constraints rule out quite a lot—in a useful way.
Static by default
The site is built with Astro and deployed to Cloudflare Pages. Astro turns the content directory into a set of pages at build time. Cloudflare serves those pages from its edge network. There is no application server to keep alive and no database to back up.
The publishing loop is deliberately short:
# Start the local writing environment
npm run dev
# Check types and content, then build static HTML
npm run check
npm run build
# Publish the dist/ directory to Cloudflare Pages
npm run deploy
Each post carries a small amount of typed frontmatter—title, description, publication date, and tags. That same data feeds the homepage, archive, RSS feed, canonical metadata, Open Graph cards, and article structured data. One source, several useful outputs.
title: A blog should be a folder of files
description: A small publishing setup built around Markdown and static HTML.
publishedAt: 2026-07-19
tags: [web, architecture, tooling]
draft: false
The important property is not the framework. It is that the content remains legible without it. If this site moves to a different generator in five years, the articles are still Markdown files with unsurprising metadata.
Let the conversation live elsewhere
An article benefits from being owned: it has a stable URL, a canonical source, and room for corrections. A discussion benefits from being shared: it needs people, competing experiences, and enough moderation to stay useful.
Trying to make one page do both creates work that does not improve the writing. So posts here have two discussion states.
Before a Hacker News thread exists, the footer offers a Submit to Hacker News link with the title and canonical URL already filled in. After submission, I add the resulting item URL to the post:
hackerNewsUrl: https://news.ycombinator.com/item?id=12345678
The footer then becomes Discuss on Hacker News and points readers to that exact thread. No comment embed, no fragile attempt to discover the thread automatically, and no dependency on Hacker News for reading the article.
What this setup does not solve
Static publishing has trade-offs. There is no browser-based CMS. A typo fix requires another build. Scheduled publishing needs a separate automation. Showing a live Hacker News comment count would require a request at build time or in the browser.
For now, those are good omissions. They keep the system proportional to the actual job. Features can earn their way in when a real constraint appears.
The first version of a blog should make the second article easier to publish. Everything else is optional.