OneBite.Dev - Coding blog in a bite size

Astro Content collections Guide (For beginner 2023)

Managing contents in Astro (static site generator) is now more fun and safe with content collections

New Astro version astro@2.0.0 introducing “content collections” that will make our life easier to work with contents/collections on static site generated website.

Official doc for Astro Content collections

What is content collections in astro?

A content collection is a directory of files under src/content folder that used as source for our “dynamic content”.

Dynamic content would be something like:

This collection will be organized nicely under src/content folder. For example:

src/content
           /blogs
           /authors

In this sample we have two content collections, 1 for blog 1 for author

Sample visual how folder might be organize from astro official site sample astro content collection

What files are allowed in content collections

Astro allows:

It’s recommended to use consistent naming scheme:

Why do you need astro content collections?

Astro content collections can help:

After using the content collections for several Astro projects, I can see it helps me:

How to start using Astro content collection?

Create a folder inside src/content, use a relevant name to your collection. Classic sample is “blog”.

/src/content/blog/

Everytime you need to create a new blog post, you can start writing the file inside here with either .md or .mdx format.

/src/content/blog/first-post.md

Feel free to organize it by subdirectories as well.

Define collection config

Now to use the benefit of type and frontmatter checking at runtime, we need to create the config file.

Create config file

Create new file named src/content/config.ts

This is a special file that Astro will automatically load and use to configure your content collections.

Sample config file

Here is sample for our blog collection. Remember you can have more than one collection.

// 1. Import utilities from `astro:content`
import { defineCollection } from 'astro:content';

// 2. Define your collection(s)
// we'll learn defineCollection next
const blogCollection = defineCollection({ /* ... */ }); 

// 3. Export a single `collections` object to register your collection(s)
// This key should match collection dir name in "src/content"
export const collections = {
  'blog': blogCollection,
};

Next we’ll learn what to put in your defineCollection method

Defining a collection schema

To make our frontmatter consistent across the files, we’ll define the frontmatter structure here.

This code will be put in the defineCollection method you’ve seen previously.

const blogCollection = defineCollection({
  type: 'content', // Astro v2.5.0 and later
  schema: z.object({
    title: z.string(),
    description: z.string(),
    tags: z.array(z.string()),
    image: z.string().optional(),
  }),
});

Explanation

Inside schema, we’ll defined z object (Astro using zod) that match our frontmatter.

This assumes, that our fronmatter consist of (sample post.md file inside one of content collection folder)

title: "blog post title"
description: "this is a description"
tags: ["share", "code"]
image: "source.png"

You can learn more about possible data types in zod documentation

Get and display All the collections (query it!)

Astro is as flexible as javascript. You can query this “database” anywhere you want on your /pages folder.

Create a new file on your pages (you can name it whatever you want), just like other routing system.

Now in between the ---

---
import { getCollection, getEntry } from 'astro:content';

// Get all entries from a collection.
// Requires the name of the collection as an argument.
// Example: retrieve `src/content/blog/**`
const posts = await getCollection('blog');

// You can console with
console.log(posts) 
---

Now you can loop this entries below the --- wherever you want to display it.

The frontmatter data available at name.data

{
  posts.map(post => {
    return (
      <a href={post.slug}>
        <h2>{post.data.title}</h2>
        <p>{post.data.description}</p>
      </a>
    )
  })
}

Show single post Astro content collection

To retrieve single collection, you can use dynamic routing system in Astro. This sample is for building static output, so we use getStaticPaths function.

For example below /pages/ folder, create new file /blog/[slug].astro

This assumes, you want your post displayed at name.com/blog/{post-slug}

Inside /blog/[slug].astro

---
import { CollectionEntry, 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();
---

// Sample only
<h1> {post.data.title } </h1>
<div>
  <Content />
</div>

Filter content collection in astro

You can do whatever you want with your content collections, including filter to your specific needs.

For example you want to display content based on the tags

// Example: Filter out content entries with `draft: true`
import { getCollection } from 'astro:content';
const publishedBlogEntries = await getCollection('blog', ({ data }) => {
  return data.draft !== true;
});

Assume you have draft in your frontmatter, and want to display non-draft only

Building for server output (SSR)

In case you don’t want to use “static output” in astro, we will use Astro.params to get the slug `

---
import { getEntry } from "astro:content";
// 1. Get the slug from the incoming server request
const { slug } = Astro.params;
if (slug === undefined) {
  throw new Error("Slug is required");
}
// 2. Query for the entry directly using the request slug
const entry = await getEntry("blog", slug);
// 3. Redirect if the entry does not exist
if (entry === undefined) {
  return Astro.redirect("/404");
}
// 4. (Optional) Render the entry to HTML in the template
const { Content } = await entry.render();
---

Frequently asked questions (FAQ)

How to exculde a file being built

If for any reason you want to exclude a file, you can start the filename with underscore (_). Reference: https://docs.astro.build/en/core-concepts/routing/#excluding-pages

astro