Content Collections
Define typed collections of Markdown content in zfb.config and load them from pages.
A content collection is a directory of Markdown (or MDX) files declared in your project config. zfb scans the directory at build time, parses each file's frontmatter against a schema you supply, and exposes the entries through a getCollection() helper your pages can call.
Declaring a collection
Collections are configured in zfb.config.ts (or zfb.config.json) under the collections key. Each entry has a name and a path:
export default {
collections: [
{
name: "blog",
path: "content/blog",
},
],
};The name is the identifier you pass to getCollection(). The path is the directory (relative to the project root) holding the entries. zfb walks that directory, treats each file as a Markdown document with frontmatter, and exposes its entries through getCollection("blog").
You can additionally supply an optional schema field — a JSON Schema subset that validates each entry's frontmatter when you run zfb check (the build itself does not enforce it). The supported keywords (type, properties, items, required, enum) are documented on the defineConfig page. The [{ name, path }] form remains supported for projects that don't need per-field validation.
Loading entries from a page
Pages use getCollection() to enumerate every entry, or filter the
result to look up a single one by slug:
import { getCollection } from "zfb/content";
export default function BlogIndex() {
const posts = getCollection("blog");
return (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
</li>
))}
</ul>
);
}import { getCollection } from "zfb/content";
export default function FeaturedPost() {
const featured = getCollection("blog").find((e) => e.slug === "hello-zfb");
if (!featured) return null;
return <featured.Content />;
}getCollection() is synchronous. The entire content snapshot is built
in Rust before any TSX module runs and embedded on globalThis.__zfb,
so there's no I/O at call time and no await to thread through. The
Rust↔JS bridge contract that backs this surface is stable and versioned
with the zfb package.
Each entry has three things you can rely on:
data— the parsed, validated frontmatter (typed against your schema).Content— a renderable React/Preact component compiled from the body. Render it as<post.and pass element-level overrides through theContent components= {. . . } / > componentsprop. This is the same contract Astro's@astrojs/mdxexposes; see MDX Components for details anddefaultComponentsrecipes.slug— derived from the file name (my-first-post.md→my-first-post). Nested directories become slash-separated slugs.
The function signature is documented at getCollection.
How parsing works
Under the hood, the zfb-content crate handles three jobs: it walks the configured directory, parses each file's YAML frontmatter, and compiles the Markdown/MDX body through an mdast → JSX-source emitter that is then handed to the existing SWC TSX → JS pipeline. The result is a JSX module per entry, addressed by a stable mdx: specifier; the page renderer evaluates that module on demand and surfaces it to your page as entry.Content.
The compilation and surface contract are stable. See MDX Components for the rendering side.