Comark Content vs Astro Content Collections

Astro Content Collections load and type content at build time inside Astro. Comark Content is a runtime content layer that works with any framework. Compare loaders, type safety, updates, and search.

TL;DR: Astro Content Collections are excellent if your whole site is Astro and your content ships with your build. Comark Content solves the same problem — typed access to Markdown collections — without the two constraints: it runs in any framework (Nuxt, Next.js, Vite, Nitro, Node, the browser) and reads content at runtime, so a Markdown push goes live without a rebuild.

At a glance

Comark ContentAstro Content Collections
FrameworkAnyAstro only
When content is processedRuntime (or build time via snapshots)Build time
Content updateLive, no rebuildRebuild
Content sourcesFilesystem, GitHub, any unstorage driver, customBuilt-in glob/file loaders, custom loaders (Content Layer API)
SchemaJSON Schema per source, optionalZod schema per collection
Type safetyGenerated from content or schemaGenerated from schema
Readingget(), list(), navigation()getCollection(), getEntry(), render()
QueryingOptional SQL queries over frontmatterFilter callbacks in JS
SearchOptional BM25 full-text searchNone built in
Components in MarkdownComark syntax, rendered at runtimeMDX / Markdoc, compiled at build
Runs in the browserYes (sqlite-wasm, snapshots)No

The core difference

Astro collections are resolved when you build. astro build runs your loaders, validates entries against Zod schemas, and bakes the result into the output:

// Astro: resolved at build time
import { getCollection } from 'astro:content'

const posts = await getCollection('blog', ({ data }) => !data.draft)

Two consequences:

  1. Content is coupled to the build. New or edited Markdown means a new build, even with on-demand rendering — the collection data itself is fixed at build time unless you use live content collections (experimental).
  2. The API only exists inside Astro. astro:content is a virtual module. You can't reuse the same content layer in a Node script, an API server, or another framework.

Comark Content is a plain TypeScript library. The same instance runs in an Astro endpoint, a Nuxt server route, a Next.js route handler, a Node script, or the browser:

import { comarkContent } from 'comark-content'
import github from 'comark-content/sources/github'

const content = comarkContent({
  source: github({ repo: 'my-org/content', branch: 'main' }),
})

const posts = await content.list()
const page = await content.get('/blog/hello')

Reads hit the source at runtime, so published Markdown is live on the next request. When you want Astro-style build-time determinism, parse once and ship a snapshot.

Loaders vs sources

Astro 5's Content Layer API introduced loaders, which pull remote content at build time. Comark Content sources cover the same ground with one difference: they're consulted at runtime, not snapshotted during build. A loader gives you remote content as of the last deploy; a source gives you remote content as of the current request, with caching controlling freshness.

FAQ

Can I use Comark Content inside an Astro project?

Yes. It's framework-agnostic TypeScript — create the instance in an Astro endpoint or middleware and render the returned tree with @comark/html or a framework island.

Do Astro Content Collections work outside Astro?

No. astro:content is a virtual module that only resolves inside an Astro build.

How do I get Zod-style validation with Comark Content?

Pass a JSON Schema to a source via its schema option. Entries are validated on load, and the schema drives generated types and query columns.

Which is faster?

Different trade-offs. Astro pays the cost at build time and serves static data. Comark Content parses lazily on first read and caches the parsed body; with a warm cache or a snapshot, reads don't reparse.