Getting Started with Astro Content Collections

February 1, 2024 6 min read

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

  1. Content Management: Non-technical users can add posts by creating markdown files
  2. Type Safety: Automatic validation and TypeScript support
  3. Performance: Only published posts are built into the site
  4. SEO: Proper meta tags and structured data automatically generated
  5. 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.