Web Dev2 min read

Astro Content Collections: A Practical Guide Beyond the Docs

Astro’s content collections are one of the best reasons to reach for Astro over a more generic static site generator. But the official docs cover the API surface, not the decisions you’ll actually run into while building something real. Here’s what I learned building this site on them.

Organize content by the URLs you want, not by convenience

It’s tempting to dump every post into a single flat folder. I structured mine as src/content/blog/<category>/<slug>.md from the start, because the loader’s glob() gives you the file path as the entry id — which means routing and categorization come almost for free.

const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
  schema: ({ image }) => z.object({ /* ... */ }),
});

Splitting the id for routing

Because post.id looks like ai/my-post-slug, a dynamic route at blog/[category]/[slug].astro can derive both params directly from it, instead of maintaining category as a separate lookup.

Let the schema do validation work for you

Zod schemas in the collection config catch mistakes at build time instead of runtime. A missing pubDate, a typo’d category, a draft field that isn’t a boolean — all of these fail the build immediately with a clear message, rather than shipping a broken page.

Images want to live near the content

The image() helper passed into the schema function lets you reference local images relative to the markdown file, and Astro handles optimization automatically. I resisted this at first — it felt like an odd import path — but it means hero images get the same build-time optimization as everything else.

Drafts are a workflow feature, not a schema feature

I keep a draft: boolean field, but the actual behavior — hide in production, show in dev — lives in a small helper function, not scattered if checks across every page that touches posts. One function, one place to change the rule.

The parts that still feel rough

Cross-collection queries (like “related posts by tag”) still require you to write the matching logic yourself — there’s no built-in relational query layer, which makes sense for a static site generator but is worth knowing going in.

Overall, content collections turn Markdown-plus-frontmatter into something that feels like a real, type-checked data layer. That’s a bigger deal than it sounds like on paper.

Alfas

Alfas

Software Engineer

I'm a software engineer who spends most days building things for the web and most nights reading about how AI is changing how we build them. This site is where I write down what I learn.

Related posts