← All posts

This site is built with Astro

1 min read
astrowebmeta

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.