Comark vs MDX
TL;DR: Both let you use components inside Markdown. MDX compiles your content to JSX at build time, so it is tied to a bundler and (in practice) to React. Comark parses component syntax at runtime into a framework-agnostic AST, so content can come from a database, a CMS, or an AI stream, and render to Vue, React, Svelte, Angular, HTML, or a terminal.
At a Glance
| Comark | MDX | |
|---|---|---|
| Component syntax | ::alert{type="info"} | <Alert type="info"> (JSX) |
| Build step | Not required, parse at runtime or build time | Required, compiles to JSX |
| Content source | Any string: file, database, CMS, API, LLM stream | Files processed by your bundler |
| Renderers | Vue, React, Svelte, Angular, Nuxt, HTML, ANSI | React (other JSX runtimes via pragma) |
| Streaming | Auto-close renders incomplete input at every frame | Not supported, expects complete documents |
| Output | Compact serializable AST | Compiled JavaScript module |
| Inline JavaScript expressions | No, values come from frontmatter or app state | Yes, full JSX expressions |
| Imports/exports in content | No, components registered in the renderer | Yes |
| Plugin ecosystem | Comark plugins + markdown-it plugins | remark/rehype plugins |
The Core Difference: Content vs Code
An .mdx file is a JavaScript module. It can import components, export values, and embed arbitrary expressions. That power has a cost: your content must go through a compiler before it can render. Shipping a typo fix means triggering a rebuild and a redeploy, and storing MDX in a database means running a compiler on the server for every document.
Comark keeps content as plain text:
::alert{type="warning"}
This action cannot be undone.
::Because parse(markdown) is a pure function returning an AST, the full CMS pattern works without a build pipeline:
- Store raw Markdown (components included) in a database or CMS
- Call
parse(markdown)on request, on the server or in the browser - Send the compact AST to the client
- Render it with a components map:
<ComarkRenderer :tree="ast" :components="{ Alert }" />
Content is live the moment it is saved.
Syntax Side by Side
import Alert from './components/Alert'
import Chart from './components/Chart'
# Dashboard
<Alert type="info">
Welcome back, with **bold** text inside.
</Alert>
<Chart data={metrics} /># Dashboard
::alert{type="info"}
Welcome back, with **bold** text inside.
::
::chart{:data="metrics"}
::Comark components are registered once in the renderer instead of imported per file. Dynamic values use :prop="expression" bindings resolved against frontmatter or application state. See the migration guide for the full mapping.
Streaming
LLMs emit Markdown token by token. MDX cannot compile an unterminated document, so streaming MDX means waiting for the full response or accepting compile errors mid-stream.
Comark's auto-close parser closes incomplete syntax (unterminated bold, unfinished code fences, half-open components) so every frame renders as valid UI:
import { autoCloseMarkdown } from 'comark'
autoCloseMarkdown('**bold text') // '**bold text**'
autoCloseMarkdown('::alert\nContent') // '::alert\nContent\n::'Each framework renderer exposes this as a streaming prop that pipes AI output straight into your component tree, custom components included.
What MDX Does That Comark Doesn't
Be aware of what you give up:
- Inline JavaScript expressions:
{2 + 3}or{new Date().toLocaleDateString()}evaluate in MDX. In Comark, compute values in application code and pass them via frontmatter, bindings, or string interpolation. - Imports and exports in content: MDX files are modules. Comark content is data; components live in your app.
- The remark/rehype ecosystem: MDX plugs into it directly. Comark has its own plugin API and can reuse markdown-it plugins, a different but comparable ecosystem.
If your content is written by developers, versioned in git, and full of bespoke logic, MDX's "content as code" model can be the right fit. If your content comes from writers, a CMS, an API, or a model, Comark's "content as data" model removes the compiler from the loop.
Migrating
The MDX migration guide maps every MDX concept (components, props, expressions, imports, exports, layouts) to its Comark equivalent, with a step-by-step checklist.
FAQ
::alert instead of <Alert>), and inline JavaScript must move into your application code. The migration guide covers every mapping; standard Markdown needs no changes.parse() runs anywhere JavaScript runs. You can parse at build time and ship the AST, parse on the server per request, or parse in the browser. Same API, your choice.@comark/react supports Server Components and Next.js, see Render Comark in React.