Comparisons

Comark vs react-markdown

Comark adds component syntax, streaming auto-close, and multi-framework rendering that react-markdown lacks. Compare architecture, component mapping, and streaming behavior.

TL;DR: react-markdown renders standard Markdown to React elements and lets you remap HTML tags to components. Comark does that too, and adds what react-markdown was never designed for: component syntax authors can write in the content itself, auto-close for token-by-token streaming, a serializable AST you can parse on the server, and renderers beyond React.

At a Glance

Comarkreact-markdown
Runtime parsingYesYes
Remap h1, a, code… to componentsYes, components mapYes, components prop
Custom components in the contentYes, ::alert{type="info"}No, element remapping only
Streaming incomplete MarkdownAuto-close renders every frame correctlyBroken formatting until syntax completes
FrameworksVue, React, Svelte, Angular, Nuxt, HTML, ANSIReact only
Parse/render splitDecoupled, parse on server and render on clientCoupled, parsing happens inside the component
Underlying parsermarkdown-exit (markdown-it compatible)remark (unified)
PluginsComark plugins + markdown-it pluginsremark/rehype plugins
FrontmatterParsed out of the boxNeeds remark-frontmatter + custom handling

Element Mapping Works the Same Way

If you use react-markdown only to restyle elements, the model transfers directly:

import Markdown from 'react-markdown'

<Markdown
  components={{
    a: (props) => <Link {...props} />,
    code: (props) => <CodeBlock {...props} />,
  }}
>
  {markdown}
</Markdown>

What react-markdown Can't Express

react-markdown maps existing HTML elements to components. It has no syntax for components that aren't HTML elements: a callout, a tabbed code group, a video embed. The usual workaround is raw HTML plus rehype-raw, which reintroduces the verbosity and XSS surface Markdown was meant to avoid.

Comark makes components part of the syntax:

::alert{type="warning"}
This is **Markdown** inside a component, with full nesting support.
::

Status: :badge[Active]{color="green"}

Writers, CMS editors, and LLMs can produce these components as plain text. No JSX, no HTML. See component syntax.

Streaming

react-markdown assumes a complete document. Feed it a stream mid-token and you get flashing asterisks, broken lists, and half-rendered code fences until the closing syntax arrives. This is the exact problem that led Vercel to build Streamdown.

Comark treats incomplete input as a first-class case. Auto-close completes unterminated syntax at every frame, and the streaming prop handles it end to end:

import { Comark } from '@comark/react'
import { useChat } from '@ai-sdk/react'

export function Message({ text, isStreaming }: { text: string, isStreaming: boolean }) {
  return <Comark streaming={isStreaming}>{text}</Comark>
}

Decoupled Parsing

react-markdown parses inside the React component, on every client that renders it. Comark separates the two: parse once (on the server, at build time, or in a worker) and render the compact AST anywhere:

server.ts
import { parse } from 'comark'

const tree = await parse(markdown) // cache it, store it, send it
client.tsx
import { ComarkRenderer } from '@comark/react'

<ComarkRenderer tree={tree} components={{ Alert }} />

The AST is plain arrays like ['h1', { id: 'hello' }, 'Hello'], cheap to serialize and cache. See the AST reference.

What react-markdown Does Well

  • The unified ecosystem: hundreds of remark/rehype plugins. Comark's answer is its own plugin API plus compatibility with markdown-it plugins; check that your must-have plugins have an equivalent before switching.
  • Maturity: react-markdown has been battle-tested for years across a huge install base.

If you render trusted, complete, standard Markdown in a React-only app and rely on specific remark plugins, react-markdown remains a solid choice.

FAQ