Introducing Content Collections
Tags: react, typescript, nextjs, webdev
Published: 2024-01-18 19:49:00

---
title: Introducing Content Collections
published: true
description: Transform your content into type-safe data collections
tags:
  - react
  - typescript
  - nextjs
  - webdev
cover_image: https://github.com/sdorra/content-collections/blob/e8dc00c/website/assets/logo.png?raw=true
# Use a ratio of 100:42 for best results.
# published_at: 2024-01-18 18:57 +0000
---

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](https://content-collections.dev) is a library that helps you transform your content into type-safe data collections.
After you have integrated [Content Collections](https://content-collections.dev) 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`.

```md
---
title: Hello World
published: true
---

# Hello World

This is a sample blog post.
```

Now we can use [Content Collections](https://content-collections.dev) to convert the markdown files into a data collection.

```ts
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],
});
```
The code above shows a simple example of the [Content Collections](https://content-collections.dev) configuration file `content-collections.ts`.
The `schema` function uses [zod](https://zod.dev) to define the structure of the frontmatter data.
[Content Collections](https://content-collections.dev) will validate and convert the Markdown files into objects with the following data structure:

```ts
type Post = {
  title: string;
  published?: boolean;
  content: string;
};
```

The `content` property contains the content of the Markdown file and the other properties are defined by the schema. [Content Collections](https://content-collections.dev) 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](https://content-collections.dev) 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.

```tsx
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>
  );
}
```

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](https://github.com/remarkjs/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](https://mdxjs.com) to compile the markdown content.

```ts
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,
    };
  },
```

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](https://contentlayer.dev), then you are right. [Content Collections](https://content-collections.dev) is heavily inspired by [Contentlayer](https://contentlayer.dev), but there are some fundamental differences.

If you want to learn more about why I have built [Content Collections](https://content-collections.dev) and have not stuck with [Contentlayer](https://contentlayer.dev), then check out this blog post at [sdorra.dev](https://sdorra.dev/posts/2024-01-15-content-collections).

## Feedback

[Content Collections](https://content-collections.dev) 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](https://github.com/sdorra/content-collections).