September 9, 2026 · Christian
Why adding a product to koo.dev costs one file
When koo.dev started, the plan was clear: BStrong is the first entry in the portfolio, but it won’t be the last. If adding the next one meant touching a route file, an array of product slugs, or the nav — the architecture would have failed before it started.
One collection, two rails
/work is driven by a single Astro content collection with a kind field:
"product" for things built end-to-end, "client" for work done for
someone else. The page groups entries by kind and renders both rails from
one getCollection() call — nothing else changes when a new entry
identifies as either one.
The schema is the contract
Every entry — title, summary, cover, coverAlt, and so on — is
validated by a Zod schema at build time. Forget a required field, or point
cover at a file that isn’t there, and astro build fails with the exact
field and reason. There’s no separate test suite for this: the build is
the test, on every pull request.
This blog works the same way, down to an optional product field that can
reference a product by its collection ID — write one that doesn’t exist,
and the build fails just as loudly.
Try it
Drop a new .mdx file in src/content/products/, fill in the frontmatter,
and it appears on /work, at its own /work/<slug> page, and in the
sitemap. No code change, no deploy step to remember.