Astro content collections

The mechanism that keeps this site’s taxonomy honest: every Markdown file belongs to a collection, and every collection has a Zod schema. Frontmatter is validated at build time — a decision without a status, a garden note with a typo’d stage, a tip with a malformed date all fail the build before they can reach production.

const decisions = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/decisions' }),
  schema: z.object({
    title: z.string(),
    status: z.enum(['proposed', 'accepted', 'superseded', 'deprecated']),
    date: z.coerce.date(),
  }),
});

Things this bought me on day one:

  • The enum on status is the ADR lifecycle documentation.
  • z.coerce.date() accepts 2026-07-19 in YAML and hands templates a real Date.
  • Collections are queryable (getCollection('decisions', filter)), which is what makes the homepage’s unified stream a ten-line function instead of a filesystem crawl.

This decision exists because of the constraints in naming things‘s sibling problem: structure that isn’t machine-enforced decays. See it live in ADR-001.