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.
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],
});
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;
};
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>
);
}
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,
};
},
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)