Day16 - Astro Series: Content Collection

A nice gradient background with a heading: "Content Collections"

Introduction

Aside from defining data inside components, importing from src, or fetching remote data, is there another way to author content? Yes!

What are Content Collections?

Content Collections were introduced in 2.0 to provide an easy-to-use management system and automated type validation for a site’s local content. Use this feature if you need to inject a large number of files into your site.

How to use Content Collections?

As mentioned in the folder structure section, src/content is a reserved folder used to store content collection-related files and configuration; it should not be used for other purposes. First create an entry folder and place files related to that entry inside. Files can be Markdown or MDX, or even YAML or JSON:

src/content/
├── newsletter/
│ ├── week-1.md
│ └── week-2.md
├── blog/
│ ├── post-1.md
│ └── post-2.md
└── authors/
├── grace-hopper.json
└── alan-turing.json

After creating entries and their content files, you can query them via the API provided by Astro.

What is the .astro folder?

When using Content Collections, you’ll notice Astro auto-generates related files for the collection configuration. These files are placed in the .astro folder. You don’t need to set up or modify anything; running astro dev or astro build will generate them automatically, and you can also run astro sync manually to generate them. If you use Git to manage the project, it’s recommended to add this folder to .gitignore to avoid committing it.

Defining collections

Collection configuration is optional, but adding extra collection definitions helps Astro validate data types. To define collections, create src/content/config.ts (it can also be .js or .mjs). Basic content looks like this:

// 1. Import utility functions provided by `astro:content`
import { defineCollection } from 'astro:content';
// 2. Define a collection
const blogCollection = defineCollection({
/* ... */
});
// 3. Output a `collections` object to register the collection; the name should be the same as the item folder.
export const collections = {
blog: blogCollection,
};

By defining collection types you can enforce accuracy and consistency of data within the collection. If rules are violated, Astro will provide errors and suggestions. Here is an example collection object:

import { z, defineCollection } from 'astro:content';
defineCollection({
type: 'content', // Added in version 2.5.0: Specify whether the data type is Markdown or a format like JSON or YAML.
(content / data)
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
}),
});

When collection content is Markdown, type is content; when it’s JSON or YAML, it’s data. The schema configuration follows the Zod docs🔗; Astro uses Zod for data validation.

Defining multiple collections

You can create multiple collections and then include them in the collections object.

const blogCollection = defineCollection({
type: 'content',
schema: z.object({
/* ... */
}),
});
const newsletter = defineCollection({
type: 'content',
schema: z.object({
/* ... */
}),
});
const authors = defineCollection({
type: 'data',
schema: z.object({
/* ... */
}),
});
export const collections = {
blog: blogCollection,
newsletter: newsletter,
authors: authors,
};

Querying collections

Astro provides two functions to query one or more content collections: getCollection🔗 and getEntry🔗.

import { getCollection, getEntry } from 'astro:content';
// Retrieves all content collections
// Requires the collection name as an argument
// For example: `src/content/blog/**`
const allBlogPosts = await getCollection('blog');
// Retrieves a single entry from a collection
// Requires the collection name and the entry `slug` (content collection) or `id` (data collection)
const graceHopperProfile = await getEntry('authors', 'grace-hopper');

Filtering collections

getCollection accepts a filtering callback to narrow results. For example: “In production, only show items where draft is not true; in non-production, show everything.”

import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});

Rendering collection content

Use map to iterate and return markup for each item. You can build a list from the fetched data and render it on the page.

---
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<ul>
{blogEntries.map(blogPostEntry => (
<li>
<a href={`/my-blog-url/${blogPostEntry.slug}`}>{blogPostEntry.data.title}</a>
<time datetime={blogPostEntry.data.publishedDate.toISOString()}>
{blogPostEntry.data.publishedDate.toDateString()}
</time>
</li>
))}
</ul>

Generating SSG routes from collection content

Similarly, use getStaticPaths, but this time the data used to build routes comes from collection entries instead of being hard-coded in the component.

src/pages/posts/[...slug].astro
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
// 1. Get blog collections
const blogEntries = await getCollection('blog');
// 2. Return individual Route names and Props to generate routes
return blogEntries.map(entry => ({
params:{ slug: entry.slug }, props: { entry },
}));
}
// 3. Then, by deconstructing Props, the content of each individual collection item is obtained and displayed.
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />

Summary

Finally, practice by doing it yourself. If you run into issues, check out my example🔗:

  • Use Astro’s defineCollection to create collections and define their data types
  • Create relevant files inside the content folder
  • Use getCollection to fetch and render content on pages
  • Use getCollection to generate routes

Further reading

Content Collections🔗 - Astro DOCS