DEV Community

Cover image for Introducing Content Collections
Sebastian Sdorra
Sebastian Sdorra

Posted on

Introducing Content Collections

Working with Markdown files in a web project can be a pain.
You have to find the files in the file system, read the content, parse the frontmatter, validate the content, and transform it into a data structure that can be used by your application.
This is a lot of work, and it is easy to make mistakes.

Content Collections is a library that helps you transform your content into type-safe data collections.
After you have integrated Content Collections into your project, you only need one configuration file, and then you can just use your content with a normal import statement.
And the best thing is, you get HMR (Hot Module Replacement) for your content.

A simple example

Let's say you have a directory content which contains markdown files for your blog posts, for example content/2021-01-01-hello-world.md.

---
title: Hello World
published: true
---

# Hello World

This is a sample blog post.
Enter fullscreen mode Exit fullscreen mode

Now we can use Content Collections to convert the markdown files into a data collection.

import { defineCollection, defineConfig } from "@content-collections/core";

const posts = defineCollection({
  name: "posts",
  directory: "content",
  include: "*.md",
  schema: (z) => ({
    title: z.string(),
    published: z.boolean().optional(),
  }),
});

export default defineConfig({
  collections: [posts],
});
Enter fullscreen mode Exit fullscreen mode

The code above shows a simple example of the Content Collections configuration file content-collections.ts.
The schema function uses zod to define the structure of the frontmatter data.
Content Collections will validate and convert the Markdown files into objects with the following data structure:

type Post = {
  title: string;
  published?: boolean;
  content: string;
};
Enter fullscreen mode Exit fullscreen mode

The content property contains the content of the Markdown file and the other properties are defined by the schema. Content Collections will not generate those types for you; they are inferred by TypeScript.
This means you can do very powerful things with the schema, such as defining nested objects or arrays, and you get all the type safety you expect from TypeScript.
Content Collections will also generate a _meta property for each object, which contains information about the location of the file.

Now, let's see how we can use the data collection in our application.

import { allPosts } from "content-collections";
import Markdown from "react-markdown";

export default function App() {
  return (
    <main>
      <h1>Posts</h1>
      <ul>
        {allPosts
          .filter((post) => Boolean(post.published))
          .map((post) => (
            <li key={post._meta.path}>
              <h2>{post.title}</h2>
              <Markdown>{post.content}</Markdown>
            </li>
          ))}
      </ul>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

The allPosts variable is our generated collection, which we can simply import. If we change the content of a markdown file, the application will automatically reload the data collection, and the UI will be updated.

The example above uses react-markdown, but you can use any library you want to render the markdown content. You can also use a transform function to modify the markdown content during the build process. Here is an example that uses MDX to compile the markdown content.

import { defineCollection, defineConfig } from "@content-collections/core";
import { compile } from "mdx";

const posts = defineCollection({
  name: "posts",
  directory: "content",
  include: "*.md",
  schema: (z) => ({
    title: z.string(),
    published: z.boolean().optional(),
  }),
  transform: async (context, { content, ...data }) => {
    const body = String(
      await compile(content, {
        outputFormat: "function-body",
      })
    );
    return {
      ...data,
      body,
    };
  },
Enter fullscreen mode Exit fullscreen mode

And the cool thing is that Content Collections is able to infer the types from the transform function too.

If you want to learn more about Content Collections, then check out the documentation.

Contentlayer

If you think that sounds like Contentlayer, then you are right. Content Collections is heavily inspired by Contentlayer, but there are some fundamental differences.

If you want to learn more about why I have built Content Collections and have not stuck with Contentlayer, then check out this blog post at sdorra.dev.

Feedback

Content Collections is still in its early stage, and I would love to receive some feedback. If you have any questions or suggestions, please open an issue on GitHub.

Top comments (0)