Astro’s Content Collections provide a powerful way to manage structured content with built-in type safety and validation. This post explores how to migrate from static blog posts to a dynamic, content-driven system.
Content Collections Architecture
Here’s how Content Collections fit into the overall Astro architecture:
flowchart TD A[Content Files] --> B[Content Collections] B --> C[Schema Validation] C --> D[Type Generation] D --> E[getCollection API] E --> F[Static Site Generation] G[src/content/blog/*.mdx] --> B H[src/content/config.ts] --> C I[TypeScript Types] --> D J[pages/blog/slug.astro] --> E K[Built Site] --> F style A fill:#1e293b style B fill:#0369a1 style F fill:#059669
Why Content Collections?
Traditional static blog implementations require creating individual .astro files for each post, making content management cumbersome for non-technical users. Content Collections solve this by:
- Type Safety: Automatic TypeScript types for your content
- Content Validation: Schema validation for consistent data
- Better DX: Hot reload and better IDE support
- Scalability: Easy to add hundreds of posts without cluttering your pages directory
Setting Up Content Collections
First, create the configuration file that defines your content schema:
// src/content/config.ts
import { defineCollection, z } from "astro:content";
const blogCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string(),
publishDate: z.date(),
tags: z.array(z.string()),
readingTime: z.string(),
draft: z.boolean().optional().default(false),
featured: z.boolean().optional().default(false),
}),
});
export const collections = {
blog: blogCollection,
};
Creating Dynamic Routes
Replace static blog post files with a dynamic route that handles all posts:
---
// src/pages/blog/[slug].astro
import { getCollection, type CollectionEntry } from "astro:content";
import BlogLayout from "@/layouts/BlogLayout.astro";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts
.filter((post: CollectionEntry<"blog">) => !post.data.draft)
.map((post: CollectionEntry<"blog">) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<BlogLayout {...post.data}>
<article class="prose prose-lg mx-auto">
<Content />
</article>
</BlogLayout>
Content Organization
Organize your content in the src/content/blog/ directory:
flowchart TD A[src/content/] --> B[config.ts] A --> C[blog/] C --> D[getting-started-astro.mdx] C --> E[content-collections-guide.mdx] C --> F[advanced-features.mdx] style A fill:#1e293b style C fill:#0369a1 style B fill:#059669
src/content/
├── config.ts
└── blog/
├── getting-started-astro.md
├── content-collections-guide.md
└── advanced-features.md
Each markdown file includes frontmatter with the required schema fields:
---
title: "Your Post Title"
description: "A compelling description"
publishDate: 2024-02-01
tags: ["astro", "tutorial"]
readingTime: "5 min read"
---
Your content here...
Benefits of This Approach
- Content Management: Non-technical users can add posts by creating markdown files
- Type Safety: Automatic validation and TypeScript support
- Performance: Only published posts are built into the site
- SEO: Proper meta tags and structured data automatically generated
- Developer Experience: Hot reload, syntax highlighting, and better tooling
Advanced Features
Content Collections support advanced features like:
- Content References: Link between different collections
- Asset Optimization: Automatic image optimization for content images
- Internationalization: Multi-language content support
- Custom Renderers: Extend markdown rendering with custom components
erDiagram
COLLECTION ||--o{ ENTRY : contains
COLLECTION {
string name
string type
object schema
}
ENTRY ||--|| FRONTMATTER : has
ENTRY ||--|| CONTENT : has
ENTRY {
string slug
string id
object data
string body
}
FRONTMATTER {
string title
date publishDate
string[] tags
boolean draft
}
CONTENT {
string markdown
string html
object[] headings
}
This system scales from a simple blog to complex content management needs while maintaining excellent developer experience and performance.