If you’re reading this, the site works. Here’s how it’s put together.
The stack
- Astro for the framework — it ships zero JavaScript by default, which is exactly right for a blog.
- Markdown + content collections for posts, with type-safe frontmatter.
- One CSS file for the entire design system. No Tailwind, no CSS-in-JS, just custom properties and patience.
- GitHub Pages for hosting, with a GitHub Action doing the deploys.
Content collections
Posts live in src/content/blog/ with a schema that catches typos at build
time:
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
Misspell pubDate and the build fails loudly instead of silently rendering
an empty page. Worth it.
The design system
The whole look — warm paper background, Fraunces for display type, the
terracotta accent — lives in src/styles/global.css as CSS custom
properties. Dark mode is a data-theme attribute flip with a preference
stored in localStorage, and the preference is read from an inline script in
<head> so there’s no flash of the wrong theme.
Code highlighting uses Shiki with dual themes wired to CSS light-dark(),
which follows the same color-scheme switch. One source of truth.
RSS
There’s a full-text-ish feed at
/rss.xml, generated by @astrojs/rss. Subscribe the old
fashioned way; no algorithm will rearrange the order for you.