General
What makes Streamdown different from react-markdown?
What makes Streamdown different from react-markdown?
- Incomplete Markdown handling — Streamdown integrates the
remendpreprocessor to complete unclosed bold, italic, code, and link markers in real-time.react-markdownhas no equivalent capability. - Block-level memoization — Streamdown splits content into blocks and memoizes each one. Only the last block is re-rendered as new tokens arrive, making it far more efficient for long streaming responses.
- Stable block keys — React keys are index-based, so already-completed blocks never unmount and remount.
- Security defaults —
rehype-sanitizeandrehype-hardenare applied out of the box. - Drop-in compatible — The
componentsprop API is identical toreact-markdown, so migration requires minimal changes.
Can I use custom components with Streamdown?
Can I use custom components with Streamdown?
components prop with the same shape as react-markdown. You can override any Markdown element:Is Streamdown compatible with react-markdown plugins?
Is Streamdown compatible with react-markdown plugins?
remarkPlugins and rehypePlugins props and ships with remarkGfm enabled by default. Most plugins written for react-markdown work without modification.Streamdown also provides first-party plugins for common use cases:- Syntax highlighting —
@streamdown/code(Shiki) - Math —
@streamdown/math(KaTeX) - Diagrams —
@streamdown/mermaid - CJK —
@streamdown/cjk
plugins prop rather than remarkPlugins/rehypePlugins.Setup
How do I configure Tailwind CSS to work with Streamdown?
How do I configure Tailwind CSS to work with Streamdown?
@source directive to your globals.css:content array in tailwind.config.js:node_modules may differ.How do I set up Streamdown in a monorepo?
How do I set up Streamdown in a monorepo?
node_modules directory for a workspace package may be hoisted to the repository root. Adjust the Tailwind @source (or content) path to point to the hoisted location:shamefully-hoist: false, each package has its own node_modules, so the path relative to your CSS entry file stays the same as a standard project.How do I add CSS variables for Streamdown's styles?
How do I add CSS variables for Streamdown's styles?
Streaming
What is the difference between streaming mode and static mode?
What is the difference between streaming mode and static mode?
mode prop:streaming(default) — Content is split into blocks and rendered incrementally. Remend completes incomplete syntax on every update. React Transitions are used to avoid blocking the UI during rapid updates.static— The full content is rendered as a single Markdown document without block splitting or remend preprocessing. Use this for already-complete content that does not change.
How does incomplete Markdown parsing work?
How does incomplete Markdown parsing work?
parseIncompleteMarkdown is enabled (the default), Streamdown calls remend on the raw Markdown string before any parsing occurs. Remend counts opening and closing markers and appends missing closing syntax — for example, **bold text becomes **bold text**.This preprocessing runs on the string level, before the unified/remark pipeline builds its AST. After remend runs, the completed string is split into blocks with parseMarkdownIntoBlocks and each block is rendered by a memoized Block component.You can configure which completions are active via the remend prop:Why does content sometimes flicker or jump during streaming?
Why does content sometimes flicker or jump during streaming?
${id}-${index}), so keys are stable as long as the number of blocks doesn’t change. When a new paragraph starts, a new block key is created, which is expected behavior.If you see unexpected remounting of already-completed blocks:- Check that you are not passing a new
componentsobject on every render — useuseMemoor define it outside the component. - Check that
rehypePluginsandremarkPluginsreferences are stable. - Check that you are not passing an inline object to
animatedon every render — Streamdown handles this, but unnecessary re-renders in the parent can cause it.
Plugins
How do I add syntax highlighting?
How do I add syntax highlighting?
@streamdown/code plugin and pass it via the plugins prop:@source path for the plugin to your globals.css.How do I add math rendering?
How do I add math rendering?
@streamdown/math plugin:katex/dist/katex.min.css in your global stylesheet or app entry point.How do I add Mermaid diagram rendering?
How do I add Mermaid diagram rendering?
@streamdown/mermaid plugin:Performance
How does Streamdown handle memoization?
How does Streamdown handle memoization?
Streamdowncomponent — memoized with a custom comparator that does shallow comparison on all props. It only re-renders whenchildren,isAnimating,mode, or other relevant props change.Blockcomponent — each block is individually memoized. Only blocks whosecontent,isIncomplete, ordirprops change are re-rendered. This means completed blocks stay frozen in the React tree while only the last (actively streaming) block updates.
components, rehypePlugins, and remarkPlugins are important. Define them outside your component or use useMemo:What are block keys and why do they matter?
What are block keys and why do they matter?
${generatedId}-${index}. Using the index rather than a content hash means a block’s identity doesn’t change as its content grows during streaming.If a content hash were used as the key, React would unmount and remount the entire block every time a new token arrived — discarding focus state, scroll position, and animation state. Index-based keys ensure React treats each update as a prop change to an existing component rather than a replacement.Security
How does Streamdown sanitize HTML?
How does Streamdown sanitize HTML?
rehype-sanitize and rehype-harden by default as part of its rehype plugin pipeline. rehype-sanitize enforces an allowlist of HTML tags and attributes, preventing script injection. rehype-harden applies additional protections against unsafe link protocols and data URIs.This is enabled automatically — you don’t need to configure anything for basic safety.How do I allow custom HTML tags?
How do I allow custom HTML tags?
Can I replace the default sanitization plugin?
Can I replace the default sanitization plugin?
rehypePlugins array to replace the defaults:rehypePlugins array, the allowedTags prop no longer extends the schema — you are responsible for the full sanitization configuration.Error scenarios
Why do I get a 'Package shiki can't be external' warning?
Why do I get a 'Package shiki can't be external' warning?
transpilePackages in next.config.ts:Why do I get a CSS loading error with Vite SSR?
Why do I get a CSS loading error with Vite SSR?
TypeError [ERR_UNKNOWN_FILE_EXTENSION] error for CSS files such as katex.min.css. Add Streamdown to ssr.noExternal in vite.config.ts:Why do I get 'Module not found: Can't resolve vscode-jsonrpc' errors with Next.js?
Why do I get 'Module not found: Can't resolve vscode-jsonrpc' errors with Next.js?
