Astro's Content Collections is a powerful feature for managing structured content like blog posts, documentation, product catalogs, etc. It provides type safety, performance optimization, and improved developer experience.
Core Concepts:
Content Collections allow you to organize content in the src/content directory and access it through a type-safe API.
Setting Up Content Collections:
-
Create Collection Configuration:
typescript// src/content/config.ts import { defineCollection, z } from 'astro:content'; const blog = defineCollection({ type: 'content', // Use Markdown/MDX schema: z.object({ title: z.string(), description: z.string(), publishDate: z.coerce.date(), tags: z.array(z.string()), image: z.string().optional(), }), }); const products = defineCollection({ type: 'data', // Use JSON/YAML schema: z.object({ name: z.string(), price: z.number(), category: z.string(), }), }); export const collections = { blog, products }; -
Create Content Files:
markdown<!-- src/content/blog/my-first-post.md --> --- title: "My First Post" description: "This is the post description" publishDate: 2024-01-15 tags: ["astro", "tutorial"] image: "/images/post-1.jpg" --- This is the post content...json// src/content/products/product-1.json { "name": "Product 1", "price": 99.99, "category": "Electronics" }
Querying Content Collections:
astro--- import { getCollection } from 'astro:content'; // Get all blog posts const allPosts = await getCollection('blog'); // Sort by date const posts = allPosts .filter(post => post.data.publishDate <= new Date()) .sort((a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()); // Get posts with specific tags const astroPosts = await getCollection('blog', ({ data }) => { return data.tags.includes('astro'); }); --- <h1>Blog Posts</h1> {posts.map(post => ( <article> <h2>{post.data.title}</h2> <p>{post.data.description}</p> <time>{post.data.publishDate.toLocaleDateString()}</time> <a href={`/blog/${post.slug}`}>Read More</a> </article> ))}
Getting a Single Entry:
astro--- import { getEntry } from 'astro:content'; import type { CollectionEntry } from 'astro:content'; // Get single entry by slug const post = await getEntry('blog', 'my-first-post'); // Or get from params const { slug } = Astro.params; const post = await getEntry('blog', slug); if (!post) { return Astro.redirect('/404'); } const { Content } = await post.render(); --- <h1>{post.data.title}</h1> <p>{post.data.description}</p> <Content />
Dynamic Routes with Content Collections:
astro--- // src/pages/blog/[slug].astro import { getCollection } from 'astro:content'; export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(post => ({ params: { slug: post.slug }, props: { post }, })); } const { post } = Astro.props; const { Content } = await post.render(); --- <h1>{post.data.title}</h1> <Content />
Using MDX:
markdown--- title: "Post with MDX" description: "Markdown with JSX support" publishDate: 2024-01-20 tags: ["mdx", "astro"] --- import { Counter } from '../../components/Counter.jsx'; This is regular Markdown content. <Counter /> You can use any component here!
Advantages of Content Collections:
-
Type Safety:
- Define content structure using Zod schemas
- TypeScript automatically infers types
- Compile-time validation
-
Performance Optimization:
- Process content at build time
- Automatically generate routes
- Avoid runtime parsing
-
Developer Experience:
- IDE autocomplete
- Type checking
- Error hints
-
Flexibility:
- Supports Markdown, MDX, JSON, YAML
- Custom schemas
- Pre-data processing
Advanced Usage:
-
Nested Directories:
shellsrc/content/ ├── blog/ │ ├── 2024/ │ │ ├── january/ │ │ │ └── post.md │ │ └── february/ │ │ └── post.md │ └── 2023/ │ └── post.md └── docs/ └── guide.md -
Custom Renderers:
typescript// src/content/config.ts import { defineCollection, z } from 'astro:content'; const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), }), // Custom renderer render: async ({ entry }) => { // Custom rendering logic return entry.render(); }, }); -
Content Transformation:
typescript// src/content/config.ts const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), date: z.coerce.date(), }), transform: async (data, id) => { // Transform data return { ...data, slug: id.replace('.md', ''), }; }, });
Best Practices:
- Use Zod schemas to define clear content structures
- Create separate collections for different content types
- Leverage TypeScript for type safety
- Use
getStaticPathsto generate dynamic routes - Process all content at build time to avoid runtime overhead
Content Collections is the best way to handle structured content in Astro, especially suitable for content-driven websites like blogs, documentation, and product catalogs.