DEV Community

Obinna Ekwuno
Obinna Ekwuno

Posted on

How to use content collection in Astro.

In case you missed it, Astro launched 2.0 with a couple of exciting announcements, one of which is the new Content collection API with Type-safety powered by Zod.

In a previous post, I wrote a summary of What's new in Astro 2.0; you can check it out for more information.

This post will look at creating Content collections and how you can start building and sourcing content in your Astro application.

We will be doing so by building a simple newsletter application, exploring creating a collection, querying the collection in components and creating routes for each markdown file in the collection.

Project setup

To follow the project in the blogpost, you can fork my content collection example project on Codesandbox. Create a fork, and you are good to go.

Pull the repo from GitHub and set up if you prefer to do this locally.

The parts that are covered is the Content/, Pages/newsletter folders. The file structure looks like and t

/
├── public/
│   └── favicon.svg
├── src/
│   ├── components/
│   │   └── Card.astro
│   ├── content/
│   │   └── newsletter
│   │       ├── post-1.md
│   │       └── post-2.md
|   ├── config.ts
│   ├── layouts/
│   │   └── Layout.astro
│   └── pages/
│       ├── newsletter
│       │   ├── [slug].astro
│       └── index.astro
└── package.json
Enter fullscreen mode Exit fullscreen mode

Setting up Content Collections in Astro

A content collection is a group of .mdx or .md files that are created under the src/content folder in Astro. The content files are type-safe and allow you to group your content in a schema and type-safe way. Once you have a collection, you can start querying your content using Astro’s built-in Content APIs.

Define collection schema

First, create a config.ts file in the src/content folder, which is the config file where you define the "collections" for your project. We describe the frontmatter schema in this file and use Zod to define types.

From the code block below, you can notice that we define the schema with some types. Doing so ensures that when we create new .md or .mdx files, the frontmatter is type-safe, and we get the benefits of Typescript in markdown.

import { z, defineCollection } from "astro:content";

// Define a collection of newsletter posts
const newsletterCollection = defineCollection({
// Define the schema 
    schema: z.object({
        title: z.string().max(100),
        date: z.date(),
        categories: z.array(z.string()),
        summary: z.string(),
        image: z.string().optional(), //Image can be optional
    })
});

// Export
export const collections = {
    //  collectionName: collection
    newsletter: newsletterCollection,
};

Enter fullscreen mode Exit fullscreen mode

Creating Markdown content in Astro

Since we are creating content collections, the markdown files must follow a "collection" type format. So in the src/content folder, create a newsletter folder this is where we add markdown files and inside those files add the frontmatter, following the schema format that is was defined.

Under the newsletter folder create a post-1.md and then define the frontmatter with respect to the schema

---
title: "Newsletter Post 1"
date: 2019-01-01T00:00:00.000Z
categories: [Newsletter, News]
summary: "This is the first newsletter post."
---
# Newsletter Post 1

// Content 
Enter fullscreen mode Exit fullscreen mode

The frontmatter is type-safe, so if you add any field that isn't defined in the schema, you will get a prompt to fix the error. Thankfully Astro has very helpful error messages to prompt. Also, notice how no image is defined because, in the schema, it is marked as optional.

Importing Content collections to components

Now that you have defined the schema and have your first post up. You can pull your content data using the getCollection from astro:content.

Then pass your collection name into the getCollection function. Doing so will give you an array that you can map through and display in a component.

You might need to restart your dev server after importing so that astro:content can be accessed.

import { getCollection } from 'astro:content';
const newsletters = await getCollection('newsletter');

// ...

{ newsletters.map((newsletter) => {
    return (
        <Card
         href={`/newsletter/${newsletter.slug}`}
         title={newsletter.data.title}
         body={newsletter.data.summary}/>
        )
    })
}
Enter fullscreen mode Exit fullscreen mode

Notice that the href for the newsletter cards opens up a path /newsletter/${newsletter.slug}. This is because, in Astro, all pages are created under the pages folder so, we create a newsletter folder under pages to make routes for each post.

You can make multiple routes for each collection that you have.

Generate Pages for Content collections

If you are familiar with creating dynamic routes, they are often depicted with [] and have an identifier to create the routes based on the identifier.
For example, it can be making each route based on the id:[name.id] or on the folder's name [Professions.name].

To create a new page for each of the newsletter entries under the Pages folder, create newsletters/[slug].astro; this will create a new page for each post.

Of course, you can also define custom slugs for your files, but we will stick with the generated ones.

First, get the collection; you will use the getCollection function again.

import { getCollection } from "astro:content";
Enter fullscreen mode Exit fullscreen mode

Then, because we want to create paths from these collections at build time, we make a function getStaticPaths. In this function, we will pass the newsletter collection into getCollection and await the response.

This response is then mapped out to create a slug for each post at build time, the params Object shows what file is rendered, as seen below:


// This function gets called at build time and generates the paths from the content folder
export async function getStaticPaths() {
    // Define the colection you are creating pages for
    const allNewsletters = await getCollection("newsletter");

    return allNewsletters.map((newsletter) => {
        return {
            params: {slug: newsletter.slug},
    // 
            props:{newsletter},
        };
    });
}
Enter fullscreen mode Exit fullscreen mode

Access props and rendering markdown for Content collections.

Now that we have defined the newsletter, we would need to access the props in this particular component, and we can do that with Astro.props to typecast this even further add a
{CollectionEntry<>} type and pass in newsletter as seen below:

---
import {CollectionEntry, getCollection } from "astro:content";
//.. 

// Access the props in this component 
const {newsletter} = Astro.props as {newsletter: CollectionEntry<"newsletter">};
const {Content} = await newsletter.render();
---
Enter fullscreen mode Exit fullscreen mode

To get the actual content from the post-1.md file, call the render function pass in the Content.

We can now render the content like so:

<main>
    <h1>{newsletter.data.title}</h1>
    <p>Category: {newsletter.data.categories.join(',')}</p>
    <Content />
    <p>
    <a href="/">Back to previous newsletters</a>
</p>
</main>
Enter fullscreen mode Exit fullscreen mode

Learn more

Astro's content collection API allows for so flexibility and type-safety when it comes to handling data. There is also a guide for migrating from File-based routing if you want to update your projects.

To get started with this example project, you can create a fork of my Content-collection API project on CodeSandbox. Happy coding 👋🏾

Top comments (4)

Collapse
 
imthedeveloper profile image
ImTheDeveloper

Glad to see this post. Recently started building an e-commerce front end with astro and its absolute 🔥

Collapse
 
ndeyros profile image
Nicolas Deyros

@obinnaspeaks great article. I'm finding difficult to create a [tag].astro page that allows me to filter the post base on their tag for a server mode. Do you know where can I find a guide for this?

Collapse
 
obinnaspeaks profile image
Obinna Ekwuno

Hey @nicolas I reckon you mean for SSR. I didn't cover it in this bit but I can update. It's similar to how you would handle a static render. Check this doc out docs.astro.build/en/guides/content...

Collapse
 
xxkhanxx77 profile image
xxkhanxx77

this article is help me