Building This Site
Every developer eventually ends up building their own site. Here’s how I built this one.
The Stack
I wanted something that would be easy to maintain long-term. Blog posts and project entries should just be Markdown files — no CMS, no database. The site should be static by default.
The choices:
- Astro 5 with
output: 'static'— generates plain HTML, Markdown content collections built in, zero JS shipped by default - Tailwind v4 — CSS-first config via
@themedirective, no config file needed - nginx in production, Astro dev server in development
- Docker + Traefik — matches the rest of my local dev setup
Content Collections
Astro 5’s content collections let me define a schema in TypeScript and then write posts as Markdown files:
src/content/
├── blog/
│ └── building-this-site.md
└── projects/
└── board-game-tracker.md
Type-safe frontmatter, automatic slug generation from filename, and a dead-simple query API:
const posts = await getCollection('blog', ({ data }) => !data.draft);
Local Dev Setup
The site runs on https://jsamackay.local.com locally using mkcert-generated wildcard certs handled by Traefik. Two compose files:
docker-compose.yml— production nginx builddocker-compose.dev.yml— dev override with Astro’s hot-reload server and volume mounts
# Dev (hot reload)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Prod build
docker compose up -d --build
Tailwind v4
The biggest change from v3: no more tailwind.config.js. Design tokens live in CSS:
@import "tailwindcss";
@theme {
--color-accent: #00ff41;
--font-family-mono: 'JetBrains Mono', monospace;
}
These automatically become utility classes: text-accent, bg-accent, font-mono. Clean.
What’s Next
- Add more projects
- Write up some of the experiments I’ve been running
- Maybe add an RSS feed