← All projects
Abstract dark blue artwork of container and orchestration logos
Project

This site: Astro on a static host

A static blog built with Astro, deployed straight from Git on every push, with no database and no server to maintain.

This site is a project too, and the smallest of them all: a handful of Markdown files, a static site generator, and a deploy step that nobody has to think about.

The constraints

  • No server to maintain. There’s enough infrastructure in my life already.
  • Write in Markdown, in a text editor, versioned alongside everything else.
  • Zero cost, hosting included.
  • Fast by default: no client framework, no unnecessary JavaScript.

A static site behind a CDN ticks all four boxes. The repository already exists, the build is one command, and the host takes it from there.

How it’s built

Astro generates static pages and only ships JavaScript to the browser when you explicitly ask for it. Here that’s a single script for the light/dark toggle. The rest is HTML and CSS.

Content goes through content collections: every post is a Markdown file whose frontmatter is validated by a Zod schema at build time.

const baseSchema = z.strictObject({
  title: z.string().min(1).max(120),
  description: z.string().min(1).max(300),
  pubDate: z.coerce.date(),
  updatedDate: z.coerce.date().optional(),
  tags: z.array(z.string().min(1)).optional(),
  draft: z.boolean().default(false),
});

strictObject is the detail that makes the whole thing reliable: a misspelled frontmatter key fails the build instead of being silently ignored. Typos get caught at build time, not while re-reading the site three weeks later.

Deployment

There is no pipeline to maintain. The host watches the repository, runs npm ci && npm run build, and serves dist/ from its CDN:

npm ci
npm run build     # astro check, then astro build
# dist/ is the whole site

A git push to main is enough, and the site is live a minute later. Pull requests get their own preview URL, which turns “does this render correctly?” into something you can answer before merging rather than after.

What I might change

Client-side search, if the number of posts ever justifies it. And per-post Open Graph images generated at build time. For now a static image does the job, and a feature you don’t write is a feature you don’t maintain.