Blog

Astro Content Collections: the blog I used to start this site

How I set up the vmerino.es blog using Astro 6 Content Collections, with local Markdown and the door open to migrate to a CMS later.

This very post exists thanks to the system it describes. Astro Content Collections let you manage local content (Markdown, MDX, JSON…) with full typing and frontmatter validation via Zod.

The structure

src/content/
  config.ts        ← defines collections and their schemas
  blog/
    first-post.md
    second-post.md

In config.ts you define the schema:

import { defineCollection, z } from 'astro:content'

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    date: z.date(),
    tags: z.array(z.string()).optional().default([]),
    draft: z.boolean().optional().default(false),
  }),
})

export const collections = { blog }

Astro validates every .md file against this schema at build time. If a required field is missing or the type doesn’t match, the build fails with a clear error.

Consuming posts

import { getCollection } from 'astro:content'

const posts = (await getCollection('blog', e => !e.data.draft))
  .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())

The draft filter keeps in-progress posts out of production.

Future CMS migration

The best thing about Content Collections is that migrating to a CMS is almost trivial. Astro 5 introduced official loaders for Contentful, Sanity, and others. The Zod schema doesn’t change — you just swap how the content is loaded. Components and pages stay untouched.