Comark Content vs Contentlayer
TL;DR: Both give you typed access to Markdown content. Contentlayer compiles local files into JSON at build time, is coupled to your bundler, and has been unmaintained since 2023. Comark Content parses Markdown at runtime from any source (filesystem, GitHub, S3, KV, custom), so content updates don't require a rebuild, and it adds SQL queries and full-text search when you need them.
At a glance
| Comark Content | Contentlayer | |
|---|---|---|
| Maintenance | Active | Unmaintained since 2023 (community fork: contentlayer2) |
| When content is processed | Runtime (or build time via snapshots) | Build time only |
| Content sources | Filesystem, GitHub, any unstorage driver, custom | Local files |
| Content update | Live, no rebuild | Rebuild and redeploy |
| Frameworks | Any: Nuxt, Next.js, Vite, Nitro, Node, browser | Next.js (official), others partial |
| Type safety | Types generated from your content | Types generated from a schema you declare |
| Markdown flavor | CommonMark + GFM + components in Markdown | Markdown / MDX |
| Querying | list() filters, optional SQL queries | Import generated arrays (allPosts), filter in JS |
| Search | Optional BM25 full-text search | None |
| Where it runs | Server, edge, browser | Build process only |
The core difference
Contentlayer is a build step. It watches content/, validates files against a schema, and emits typed JSON your app imports:
// Contentlayer: data was generated at build time
import { allPosts } from 'contentlayer/generated'
const post = allPosts.find((p) => p.slug === 'hello')Every content change requires a rebuild. Content that isn't a local file (a separate repo, a bucket, an API) is out of scope.
Comark Content is a runtime layer. It reads from a source when your app asks:
// Comark Content: read at request time
import { comarkContent } from 'comark-content'
import github from 'comark-content/sources/github'
const content = comarkContent({
source: github({ repo: 'my-org/content', branch: 'main' }),
})
const post = await content.get('/blog/hello')Editors push Markdown, the next request serves it. No rebuild, no redeploy. If you prefer build-time semantics, parse once and ship a snapshot: you keep the typed API without the runtime source.
Migrating from Contentlayer
Contentlayer's repository stopped receiving updates in 2023. If you're moving off it:
- Document types → sources. A
defineDocumentTypeper folder becomes one source:fs('./content'). Frontmatter fields are picked up as-is, or declare a JSON Schema per source via itsschemaoption for validation and typedget()/list(). allPosts→list(). Generated arrays becomeawait content.list(), filtered by path or frontmatter. For structured queries, add the SQL query plugin.- MDX components → Comark components.
<Alert type="info">becomes::alert{type="info"}— plain text, no compilation, rendered with your framework's<MarkdownDocument>. .mdxcompilation → runtime parse. There is no compile step to configure.content.get()returns an AST your renderer consumes directly.
FAQ
Is Contentlayer still maintained?
No. The original contentlayer package has had no releases since 2023. A community fork, contentlayer2, continues maintenance for Next.js users.
Can Comark Content work at build time like Contentlayer?
Yes. Parse your source once, serialize a snapshot, and hydrate it at runtime — including fully in the browser. You get build-time determinism with the same API.
Does Comark Content validate frontmatter like Contentlayer schemas?
Yes. Pass a JSON Schema to a source via its schema option. Documents are validated on load, and the schema drives generated types and SQL query columns.
CLI
The comark-content binary — locate your Content config and generate source types.
vs Astro 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.