markstream-react
v0.0.49
Published
React Markdown renderer optimized for large documents with progressive Mermaid rendering, streaming diff code blocks, and fast real-time preview. Built on stream-markdown AST for consistent rendering across frameworks. Perfect for documentation sites, AI
Maintainers
Keywords
Readme
markstream-react
React renderer that consumes the structured AST output from stream-markdown-parser and renders it with lightweight semantic HTML components. This is the React counter-part to the Vue renderer that powers markstream-vue.
Development
pnpm --filter markstream-react devBuild
pnpm --filter markstream-react build
pnpm --filter markstream-react build:analyze
pnpm --filter markstream-react size:checkUsage
import NodeRenderer from 'markstream-react'
import 'markstream-react/index.css'
export default function Article({ markdown }: { markdown: string }) {
return (
<NodeRenderer content={markdown} />
)
}If your app scales root font size on mobile (html / body), use markstream-react/index.px.css to prevent rem-based global scaling side effects.
You can also pass a pre-parsed nodes array if you already have AST data.
Streaming best practices
- For high-frequency SSE / token streaming, prefer parsing outside the component and pass
nodesinstead of reparsing the fullcontentstring every chunk. - Keep
viewportPriorityenabled unless you explicitly want eager rendering. Mermaid / Monaco / D2 blocks now stay idle while offscreen and resume when they approach the viewport.
import NodeRenderer from 'markstream-react'
export default function StreamView({ nodes, final }: { nodes: any[], final: boolean }) {
return (
<NodeRenderer
nodes={nodes}
final={final}
viewportPriority
deferNodesUntilVisible
/>
)
}Heavy-node prop forwarding
NodeRenderer can forward renderer-level props directly into Mermaid / D2 / Infographic blocks:
<NodeRenderer
content={markdown}
mermaidProps={{
showHeader: false,
renderDebounceMs: 180,
previewPollDelayMs: 500,
}}
d2Props={{ progressiveIntervalMs: 500 }}
infographicProps={{ showHeader: false }}
/>Notes:
- These props are forwarded to the built-in Mermaid / D2 / Infographic blocks and to custom
mermaid/d2/infographicoverrides registered withsetCustomComponents(...). viewportPriorityapplies to those heavy nodes too, so offscreen graphs will not keep doing background work while the text stream is still updating.
Language-specific code block overrides
You can register a custom component under a fenced language key without wrapping the generic code_block renderer:
import type { NodeComponentProps } from 'markstream-react'
import { setCustomComponents } from 'markstream-react'
function EChartsBlockNode(props: NodeComponentProps<any>) {
return <div data-language={String(props.node?.language)}>{String(props.node?.code || '')}</div>
}
setCustomComponents('docs', {
echarts: EChartsBlockNode,
})Notes:
echartsonly catches fences whose language isecharts.- Code block routing priority is exact language key -> built-in
mermaid/d2/infographicroutes ->code_block. - Custom
mermaid/d2/infographicoverrides keep their specialized top-level props; other custom language keys use the normal custom component contract (node,ctx,renderNode, and friends).
Mermaid tuning
Common mermaidProps keys for streaming scenarios:
renderDebounceMs: delay progressive work during rapid token bursts.contentStableDelayMs: how long source mode waits before auto-switching back to preview when content stabilizes.previewPollDelayMs: initial delay before preview polling tries to upgrade a partial preview into a full render.previewPollMaxDelayMs: cap for preview polling backoff.previewPollMaxAttempts: maximum retry count while the Mermaid source is still incomplete.
Bundle size notes
- Optional peers are not bundled; install only what you use (
stream-monaco,stream-markdown,mermaid,katex, etc.). - Infrequent language icons are split into an async chunk and loaded on demand.
- To avoid first-hit fallback icons, preload once when the app is idle:
import { preloadExtendedLanguageIcons } from 'markstream-react'
if (typeof window !== 'undefined')
void preloadExtendedLanguageIcons()Tailwind
- Non-Tailwind projects: keep importing
markstream-react/index.css(includes precompiled utilities for the renderer). - Tailwind projects (avoid duplicate utilities): import
markstream-react/index.tailwind.cssand addrequire('markstream-react/tailwind')to yourtailwind.config.jscontent.
Custom components (e.g. <thinking>)
Custom tag-like blocks are exposed as nodes with type: 'thinking' (the tag name, no angle brackets) when you register the tag in customHtmlTags or register a custom component mapping for it.
import type { NodeComponentProps } from 'markstream-react'
import NodeRenderer, { setCustomComponents } from 'markstream-react'
function ThinkingNode(props: NodeComponentProps<{ type: 'thinking', content: string }>) {
const { node, ctx } = props
return (
<div className="thinking-node">
<div className="thinking-title">Thinking</div>
<NodeRenderer
content={node.content}
customId={ctx?.customId}
isDark={ctx?.isDark}
typewriter={false}
batchRendering={false}
deferNodesUntilVisible={false}
viewportPriority={false}
maxLiveNodes={0}
/>
</div>
)
}
setCustomComponents('chat', { thinking: ThinkingNode })Type exports
markstream-react now exposes the core public types directly from the package root, including:
NodeRendererPropsNodeComponentPropsRenderContextRenderNodeFnCustomComponentMapCodeBlockMonacoOptions
