Blog
How I Built This Portfolio with SDD — Slice 002
Why This Post Exists
This is the story of how I built slice 002 of this portfolio (the blog system) using Spec-Driven Development. Not a tutorial on SDD itself — that comes in other posts. This is a case study: the decisions I made, the constraints I set, and how they shaped the code.
The Starting Point
I had a foundation (slice 001: home, about, design system). Now I needed a blog. The easy path: spin up Astro, create a blog/ folder, write some posts. Done in an afternoon.
Instead, I did something that looked wasteful: I wrote a constitution, a specification, and a plan — before touching the code. For a blog. It felt like overkill.
It wasn’t.
Slice 002: Blog System
The Constitution (What’s Non-Negotiable?)
Before I touched a single .astro file, I asked: What rules do I never want to break? I wrote them down:
- Static-first: No JavaScript by default. Posts are HTML + CSS.
- Content as contract: Frontmatter is validated by schema (Zod). Invalid frontmatter fails the build, period.
- Drafts never ship: A post marked
draft: trueis invisible in production, the sitemap, and RSS — no exceptions. - Semantic design tokens: Color, spacing, typography come from a token system, not hardcoded values.
These became law. They weren’t guidelines — they were gates. If the build violated them, the build failed.
The Spec (What Am I Building?)
Next, I described what the blog needed to do:
Three user stories:
- Discover: A reader visits
/blogand sees recent posts (title, date, reading time, excerpt, tags) - Read: Click a post, see full content with metadata and navigation links
- Filter: Click a tag, see only posts with that tag
Functional requirements:
- RSS feed at
/rss.xmlwith auto-discovery - Sitemap includes all published posts and tag pages (but excludes drafts)
- Per-post SEO: unique title, meta description, canonical URL, OG image, JSON-LD
- Reading time calculated automatically
- Tag pages styled consistently with posts
Success criteria:
- Build passes with Zod validation
- Lighthouse ≥ 95 on Performance, Accessibility, SEO
- All routes work with JavaScript disabled
The Plan (How Do I Build It?)
With the spec locked, I chose the stack:
- Astro 5 for static generation
- Content Collections with Zod schema for blog posts
- Tailwind 4 + Typography plugin for prose styling
- @astrojs/rss and @astrojs/sitemap for feeds and discoverability
I also documented:
- The exact shape of blog post frontmatter (title, description, pubDate, tags, image, draft, etc.)
- Route structure:
/blog,/blog/<slug>/,/blog/tags/<tag>/ - Components to create: PostLayout, BlogListing, TagPage
The Tasks (What’s the Order?)
I broke the plan into 22 tasks, ordered by dependencies. But here’s where it got smart: I labeled each task with a model.
15 tasks as Haiku — mechanical work: plumbing, data, setup. The build gate catches errors.
7 tasks as Sonnet — aesthetic & judgment: layouts, prose styling, accessibility, edge cases. “It compiles” ≠ “it looks premium.”
Why? Because once SDD locked down the spec, most implementation is just assembly. Haiku is cheap and fast at assembly. Sonnet handles the judgment calls. This split meant I could finish without burning budget on routine work.
The phases:
Phase 1: Add @astrojs/rss (the one new dependency) [Haiku]
Phase 2: Content collection + schema + sample posts [Haiku]
Phase 3: Build /blog listing and individual post pages [Haiku + Sonnet for layout/styling]
Phase 4: Add RSS feed, sitemap, SEO verification [Haiku]
Phase 5: Build tag pages and validate tag links [Haiku + Sonnet for design]
Phase 6: Polish pass — accessibility audit, Lighthouse, edge cases [Haiku for testing, Sonnet for fixes]
Each task was tiny enough to validate before the next. I tested drafts, empty blogs, UTF-8 characters in titles, missing images — all before “done.” The model split meant different tools for different work, same verification gate for all of it.
What I Learned
Clarity is expensive upfront, cheap later.
The week I spent writing constitution + spec + plan seemed like a detour. But:
- No false starts (I didn’t build the wrong thing and rip it out)
- No ambiguity (when the spec says “drafts excluded from RSS,” the code follows exactly)
- No debate (the constitution settled design questions before implementation)
Validation catches problems early.
Zod schema validation meant that invalid frontmatter failed the build. A typo in pubDate: "2026-06-07" would break the site. That’s not a bug — that’s a feature. It meant I could never ship broken content.
Dependencies are explicit.
Task 008 (PostLayout component) depended on Task 005 (reading time utility), Task 006 (BaseLayout extension), and Task 007 (prose styling). By laying them out, I knew the exact order to work in. No guessing, no backtracking.
The spec is the contract.
When I got to implementation, the spec was the source of truth. Not my memory, not vague notes. The spec says drafts are excluded from RSS, so: ✓ RSS filters by !draft. The spec says Lighthouse ≥ 95, so: ✓ I ran the audit and confirmed 100/100/100.
What’s Next?
This portfolio continues in slices:
- Slice 001 ✅: Foundation (home, about, design system)
- Slice 002 ✅: Blog (posts, tags, RSS)
- Slice 003 🔄: Projects showcase (coming next with the same approach)
Each slice follows the same path: constitution → spec → plan → tasks → implement → validate.
The blog isn’t the end goal. It’s proof that SDD scales — from a small feature (blog) all the way to complex systems. And that’s what makes it interesting.
Want to understand the methodology itself? Read “Before the First Line of Code: Everything I Did Before Writing a Single Line” or explore how I route models to save tokens with “Haiku for the Plumbing, Sonnet for the Soul.”