DeltaKitDeltaKit

Comparison

Honest performance benchmarks and feature comparison against react-markdown.

@deltakit/markdown is faster and smaller than react-markdown, but it also does less. This page presents honest numbers with full context so you can make an informed decision.

Bundle Size

This is the most objective comparison — no caveats needed.

Metric@deltakit/markdownreact-markdown
Minified12.0kb114.8kb
Minified + gzipped3.8kb35.3kb
Runtime dependencies011

react-markdown is 9.3x larger because it bundles the full unified/remark/rehype pipeline. @deltakit/markdown has zero runtime dependencies — the parser is built from scratch.

Note on streaming stability: @deltakit/markdown buffers partial block-level markers and early table lines during streaming to prevent choppy rendering. Empty headings, orphan list bullets, and standalone table headers never flash in the DOM.

Render Performance

Both libraries render the same markdown content via renderToStaticMarkup. Measured with vitest bench (tinybench).

Benchmark@deltakit/markdownreact-markdownSpeedup
Short paragraph (~60 chars)174,000 ops/s25,000 ops/s~7.0x
Mixed content (~1.2kb)6,825 ops/s1,040 ops/s~6.6x
Streaming simulation (141 re-renders)76 ops/s16 ops/s~4.7x

Caveats

These numbers are real, but context matters:

  • @deltakit/markdown produces clean semantic HTML. Blocks render directly as semantic elements (<h1>, <p>, <pre>, etc.) without wrapper <div> or <span> elements. Less DOM work naturally means faster rendering. This is not a same-output comparison.
  • react-markdown runs a full CommonMark pipeline. It parses via micromark, transforms through the remark/rehype AST pipeline, and produces spec-compliant HTML. We run a simplified regex-based parser. Being faster while doing less work is expected, not remarkable.
  • The streaming simulation uses renderToStaticMarkup. This does NOT demonstrate our React.memo block memoization advantage. In a real browser with React reconciliation, settled blocks would usually be skipped unless later parsing extended the same block — but that's difficult to benchmark accurately in a synthetic test.

Parse-Only Performance

We also benchmark our parser (parseIncremental) against micromark, the parser engine that react-markdown uses internally.

Benchmark@deltakit/markdownmicromarkSpeedup
Short paragraph3,720,000 ops/s26,500 ops/s~140x
Mixed content (~1.2kb)219,700 ops/s1,351 ops/s~163x
Streaming sim (141 parses)2,080 ops/s20.4 ops/s~102x

Caveats

  • Different output formats. parseIncremental produces a Block[] array (lightweight AST). micromark produces a full HTML string with spec-compliant output. These are fundamentally different amounts of work.
  • Different feature coverage. micromark implements the full CommonMark specification. Our parser implements a subset. Fewer features means fewer code paths means faster execution.

The parse-only numbers show our parser is fast, but the magnitude of the speedup is partly because we're solving a simpler problem.

Performance at Scale

@deltakit/markdown is optimized for long chat transcripts with many rendered messages. Key performance characteristics:

ScenarioThreshold
Parse ~15KB document (mixed blocks)< 50ms
Parse 50 messages in sequence< 2s
Incremental stream parse (per 100-char step)< 10ms
200-item list parse< 30ms
50KB+ document parse< 100ms

DOM Efficiency

The renderer produces clean semantic HTML with no wrapper bloat:

  • No wrapper <div> around blocks — headings, paragraphs, code, lists render as direct semantic elements
  • No wrapper <span> around inline tokens — text, bold, italic, code render without extra span wrappers
  • Single shared <style> tag — streaming CSS is injected once via a ref-counted singleton, not per component instance
  • Static Markdown component — for completed messages, skips all streaming overhead (batching, debouncing, opacity transitions)

These optimizations keep the browser layout/paint/style-recalculation cost low even with hundreds of rendered messages.

Feature Comparison

This is where react-markdown clearly has the advantage. It's a mature, battle-tested library with full spec compliance.

Features both libraries support

Feature@deltakit/markdownreact-markdown
Headings (h1-h6)YesYes
ParagraphsYesYes
Bold (**text**)YesYes
Italic (*text*)YesYes
Inline codeYesYes
Fenced code blocksYesYes
LinksYesYes
ImagesYesYes
Unordered listsYesYes
Ordered listsYesYes
BlockquotesYesYes
Horizontal rulesYesYes
StrikethroughYesVia remark-gfm
TablesPartialVia remark-gfm
AutolinksYesYes

Features only react-markdown supports

FeatureStatus in @deltakit/markdown
Full CommonMark complianceNot supported
Nested blockquotesNot supported
Reference links ([text][id])Not supported
HTML entities (&amp;, &mdash;)Not supported
Escape sequences (\*not italic\*)Not supported
Plugin ecosystem (remark/rehype)Not supported
HTML passthrough (rehypeRaw)Not supported

Features only @deltakit/markdown supports

FeatureDescription
Block-level React.memoSettled blocks normally avoid re-rendering while the active block updates
Incomplete syntax bufferingRaw **, `, [, and incomplete table starts stay hidden during streaming
Code block skeletonPending code blocks render as empty <pre> shells, not paragraphs
Render batching (batchMs)Configurable DOM update frequency (8ms, 16ms, 32ms)
Static Markdown componentLightweight renderer for completed messages — no batching, debouncing, or streaming styles
Headless hookuseStreamingMarkdown for custom rendering
Framework-agnostic parserparseIncremental from @deltakit/markdown/core
Singleton style injectionOne shared <style> tag regardless of instance count
Zero runtime dependenciesNo remark, rehype, unified, micromark, vfile

When to Use Which

Use @deltakit/markdown when:

  • You're rendering AI-streamed markdown in real time
  • Flicker-free rendering matters to your users
  • Bundle size is a concern
  • You don't need the full CommonMark spec or plugin ecosystem

Use react-markdown when:

  • You're rendering static (non-streaming) markdown
  • You need full CommonMark compliance
  • You need remark/rehype plugins (math, syntax highlighting, sanitization)
  • You need HTML passthrough support

Both are good tools. They solve different problems.

Running the Benchmarks

The benchmarks are included in the package source. To run them yourself:

cd packages/markdown
pnpm run bench

To save results as JSON for regression tracking:

pnpm run bench:save

Results are written to benchmarks/results/latest.json.

On this page