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 at build time. The supported keywords (type, properties, items, required) are documented on the <code>defineConfig</code> 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 getEntry()
to look up a single one by slug:
import { getCollection, getEntry } 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 { getEntry } from "zfb/content";
export default function FeaturedPost() {
const featured = getEntry("blog", "hello-zfb");
if (!featured) return null;
return <featured.Content />;
}
Both calls are 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 signatures live in <code>getCollection</code>
and the matching getEntry.
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.