Content collections give you a type-safe way to work with structured content in Aero. Define a schema, query items at build time with getCollection(), and render Markdown with render(). Aero validates frontmatter against your schema and hot-reloads when content changes.
Define a collection
Declare collections in content.config.ts at the project root:
import { defineCollection, defineConfig } from '@aero-js/content'
import { z } from 'zod'
export const docs = defineCollection({
name: 'docs',
directory: 'client/content/docs',
schema: z.object({
title: z.string(),
date: z.date(),
}),
})
The content API uses Standard Schema, so you can use Zod, ArkType,
Valibot, or any spec-compliant validator.
Query a collection
Use getCollection() inside a <script is:build> block to fetch all items:
client/pages/docs/index.html
<script is:build>
import { getCollection } from 'aero:content'
const allDocs = await getCollection('docs')
allDocs.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
</script>
<ul>
<li data-for="{ const doc of allDocs }">
<a href="/docs/{ doc.id }">{ doc.data.title }</a>
</li>
</ul>
Each item has an id (derived from the filename) and a data object with validated frontmatter fields.
Render Markdown
Call render() to convert a collection item’s Markdown body to HTML:
client/pages/docs/[slug].html
<script is:build>
import { getCollection, render } from 'aero:content'
export async function getStaticPaths() {
const docs = await getCollection('docs')
return docs.map(doc => ({
params: { slug: doc.id },
props: doc,
}))
}
const doc = Aero.props
const { html } = await render(doc)
</script>
<h1>{ doc.data.title }</h1>
<article>{ html }</article>
Single-file Markdown import
For simple cases where you need to import a single Markdown file without defining a full collection, Aero supports direct Markdown imports in build scripts. See the content collections guide for details.
In development, edits to Markdown files or content.config.ts trigger a hot update so Vite
refreshes importers without a full page reload.