React Markdown alternatives: Benchmarking 3 streaming renderers A benchmark of four React Markdown renderers using recorded deepseek/deepseek-chat responses found that react-markdown 10.1.0 incurred a substantial rendering cost on a 22,229-character reply containing six code blocks when syntax highlighting was enabled under CPU throttling, while shorter fixtures gave little reason to switch. The comparison, run in a React 19 replay app against Streamdown 2.7.0, markstream-react 2.0.19 and Comark 0.7.0, replayed 1,993 recorded chunks for the long fixture and used an automated check for visible syntax artifacts from incomplete Markdown. The author concluded that streaming renderers need testing with each project's own configuration, since the shorter replies showed little difference between the libraries. A React chatbot that streams Markdown has to render a document while it is still being written. Each incoming chunk can extend a paragraph, finish a code block, or turn a line of text into a table. react-markdown https://github.com/remarkjs/react-markdown handles the conversion to React elements. In a straightforward implementation, the app appends each chunk to a string and passes the accumulated text back to the component. That causes repeated parsing and plugin work as the reply grows. I wanted to know when that work becomes expensive enough to justify a different renderer. I compared react-markdown with Streamdown https://github.com/vercel/streamdown , Markstream’s React package https://github.com/Simon-He95/markstream-vue/tree/main/packages/markstream-react , and Comark https://github.com/comarkdown/comark , using recorded model responses in a shared replay app. The shorter replies gave me little reason to switch. The long reply, which included six code blocks, exposed a substantial cost when react-markdown used syntax highlighting under CPU throttling. The results also showed why a streaming renderer needs testing with your own configuration. If you need an introduction to the baseline, see our guide to using react-markdown https://blog.logrocket.com/how-to-safely-render-markdown-using-react-markdown/ . When react-markdown receives updated text, it parses that input and runs the configured plugins again. React then reconciles the resulting elements and commits the necessary DOM changes https://react.dev/learn/render-and-commit . Repeated parsing can become expensive even when much of the existing DOM stays in place. The long fixture contained 1,993 recorded chunks. That means 1,993 calls to the replay’s state setter; React’s scheduling determines how those updates become renders and commits. Incomplete syntax adds another complication. For example, the accumulated response might end with an open code fence: javascript const total = items.reduce sum, item = Or a table might arrive in pieces: | Renderer | Size | | --- Streaming renderers can apply rules for incomplete Markdown while the response arrives. I tested the cost of updating the document and used an automated check to look for a few visible syntax artifacts. I saved four responses from deepseek/deepseek-chat through OpenRouter, including each chunk and its arrival timestamp: | Fixture | Characters | Content | |---|---|---| | Short | 1,963 | Plain text | | Medium | 4,703 | A code block and a table | | Long | 22,229 | Six code blocks and two tables | | Diagram | 4,599 | A Mermaid diagram | The React 19 replay app https://github.com/marvelken/Open-source-markdown-streaming gives each renderer its own page with shared controls, a chat bubble, and base styles. Replays use local fixtures, so model response times and live network conditions are outside this comparison. A shared hook appends each chunk to React state: js onChunk: accumulated, isDone = { performance.mark "chunk-append" ; settersRef.current.setContent accumulated ; if isDone { settersRef.current.setDone true ; } }, I tested react-markdown with remark-gfm for tables, then added rehype-highlight for a second configuration: