| name | markstream-react |
| description | Integrate the beta markstream-react package into a React 18+ or Next app. Use when Codex needs to add the React renderer, choose the root, `next`, or `server` entrypoint, import CSS correctly, choose between `content` and `nodes`, keep client boundaries safe, add renderer-local `streamingComponents` or `htmlComponents`, use parser transforms, or prepare a repo for `react-markdown` migration. |
Markstream React
Use this skill when the host app is React or Next and the task is to wire Markstream safely.
Workflow
- Confirm the repo is React 18+ or a compatible Next host and accepts a beta renderer API.
- Install
markstream-react plus only the requested optional peers.
- Import
markstream-react/index.css from the app shell or client entry.
- Choose the entrypoint that matches the rendering boundary.
- Use
markstream-react for the normal client renderer.
- Use
markstream-react/next for the Next-specific component surface.
- Use
markstream-react/server for server rendering without client hooks.
- Start with
content.
- For streaming or high-frequency AI output, keep
content and use built-in smooth streaming first.
smoothStreaming="auto" is the default and activates when typewriter={true} or maxLiveNodes <= 0.
typewriter only controls the blinking cursor and defaults to false.
fade controls node enter and streamed-text fade animations and defaults to true.
- Streaming vs recovering history: in chat UIs the same renderer starts streaming and later switches to history when
final={true}.
- Streaming:
smoothStreaming="auto", fade={false}, typewriter={true}. Smooth pacing handles gradual appearance; fade would flicker.
- Recovering history:
smoothStreaming={false}, fade={true}, typewriter={false}. Content is already complete — pacing would slow it down, but fade gives a polished entry animation.
- Dynamic switch:
smoothStreaming={isStreaming ? 'auto' : false}, fade={!isStreaming}.
- Move to
nodes + final only for worker-preparsed content, shared AST stores, or custom AST control.
- Remember that
htmlPolicy now defaults to safe, and Mermaid strict mode is on by default through mermaidProps.
- Respect SSR boundaries in Next.
- Prefer
use client, dynamic imports with ssr: false, or other client-only boundaries when browser-only peers are involved.
- Use renderer-local components before custom parser work.
streamingComponents receives parser-backed NodeComponentProps, supports incomplete custom tags, and automatically contributes its keys to the effective customHtmlTags list.
htmlComponents receives sanitized HTML attributes plus children; it does not receive the parser node/loading contract.
- Prefer
defineStreamingComponents(...) and defineHtmlComponents(...) for typed maps.
- Keep scoped
setCustomComponents for built-in node overrides or shared compatibility registration.
- Use
parseOptions transforms only when migration requires token or AST changes.
- Validate with the smallest useful dev, build, or typecheck command.
Default Decisions
- Renderer wiring first, migration cleanup second.
- If the repo already uses
react-markdown, pair this skill with markstream-migration.
- Prefer
content with built-in smooth streaming for most AI chat / token streaming surfaces.
- Streaming vs recovering history: when a chat message transitions from streaming to history (e.g.
final becomes true), switch props dynamically — smoothStreaming="auto", fade={false} for streaming; smoothStreaming={false}, fade={true} for history. See docs/guide/ai-chat-streaming.md for full examples.
- Move to
nodes only when another layer owns parsing or AST transforms.
- Prefer renderer-local custom-tag maps over global registry mutation.
- Prefer the smallest client-only boundary that solves the SSR issue.
- Avoid
smoothStreaming={true} for first-screen SSR content unless intentionally starting from blank; auto mode uses the mounted gate.
- Keep
htmlPolicy="safe" and Mermaid strict mode unless the request is preserving trusted legacy rendering.
- If a trusted surface needs older behavior, use
htmlPolicy="trusted" and mermaidProps={{ isStrict: false }} only on that surface and explain why.
Useful Doc Targets
docs/guide/react-quick-start.md
docs/guide/react-installation.md
docs/guide/react-components.md
docs/guide/react-next-ssr.md
docs/guide/react-markdown-migration.md
docs/guide/component-overrides.md