You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/guide/react-components.md
+69-1Lines changed: 69 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
markstream-react provides the same powerful components as markstream-vue, but built for React. All components support React 18+ with full TypeScript support.
4
4
5
-
The root `markstream-react`, `markstream-react/next`, and `markstream-react/server` entrypoints all ship declaration files. Shared renderer and component types such as `NodeRendererProps`, `NodeRendererCodeBlockProps`, `NodeComponentProps`, `RenderContext`, `RenderNodeFn`, `CustomComponentMap`, `CodeBlockMonacoOptions`, `MarkdownCodeBlockNodeProps`, `ListItemNodeProps`, `HtmlPreviewFrameProps`, `TooltipProps`, `TooltipPlacement`, and `LinkNodeStyleProps` can be imported directly from the entrypoint you use.
5
+
The root `markstream-react`, `markstream-react/next`, and `markstream-react/server` entrypoints all ship declaration files. Shared renderer and component types such as `NodeRendererProps`, `NodeRendererCodeBlockProps`, `NodeComponentProps`, `StreamingComponentMap`, `HtmlComponentMap`, `RenderContext`, `RenderNodeFn`, `CustomComponentMap`, `CodeBlockMonacoOptions`, `MarkdownCodeBlockNodeProps`, `ListItemNodeProps`, `HtmlPreviewFrameProps`, `TooltipProps`, `TooltipPlacement`, and `LinkNodeStyleProps` can be imported directly from the entrypoint you use.
6
6
## Main Component: MarkdownRender
7
7
8
8
The primary component for rendering markdown content in React.
@@ -21,6 +21,8 @@ The primary component for rendering markdown content in React.
21
21
|`final`|`boolean`|`false`| Marks end-of-stream; stops emitting streaming `loading` nodes |
22
22
|`parseOptions`|`ParseOptions`| - | Parser options and token hooks (only when `content` is provided) |
|`streamingComponents`|`StreamingComponentMap`| - | Renderer-local HTML-like tag components that receive `NodeComponentProps` and are automatically added to the parser's effective `customHtmlTags`|
25
+
|`htmlComponents`|`HtmlComponentMap`| - | Renderer-local HTML-like tag components for the raw/dynamic HTML path; components receive sanitized HTML attributes and `children`|
24
26
|`htmlPolicy`|`'safe' \| 'escape' \| 'trusted'`|`'safe'`| Controls `html_block` / `html_inline` rendering. `safe` blocks active/embed/form tags, `escape` shows literal HTML text, and `trusted` restores the broader trusted HTML behavior while still stripping scripts and unsafe attrs. |
|`debugPerformance`|`boolean`|`false`| Log parse/render timing and virtualization stats (dev only) |
@@ -148,6 +150,72 @@ This is markstream-react.`
148
150
}
149
151
```
150
152
153
+
## Custom HTML-like Components
154
+
155
+
For new React code, prefer renderer-local component maps when your content contains HTML-like custom tags.
156
+
157
+
Use `streamingComponents` when the component needs the parser-backed streaming node contract. Keys are normalized like `customHtmlTags`, automatically added to the parser's effective custom tag list, and work with incomplete tags while content is streaming.
158
+
159
+
Use `htmlComponents` when the component should render through the raw/dynamic HTML path and receive sanitized HTML attributes plus `children`. Attribute values are converted to primitive prop values, but attribute names are preserved from the source HTML, so `class` remains `class`. These components may still rerender while content streams, but they do not receive `node.loading` or the parser node contract.
160
+
161
+
For typed component definitions, prefer `defineStreamingComponents(...)` and `defineHtmlComponents(...)`. `StreamingComponentMap` and `HtmlComponentMap` describe the broad runtime map shape accepted by the renderer; the helper functions perform the per-entry contract checks that catch mixing parser-backed `NodeComponentProps` components into the HTML props path.
function App({ content, isDone }: { content:string, isDone:boolean }) {
204
+
return (
205
+
<MarkdownRender
206
+
content={content}
207
+
final={isDone}
208
+
streamingComponents={streamingComponents}
209
+
htmlComponents={htmlComponents}
210
+
/>
211
+
)
212
+
}
213
+
```
214
+
215
+
`customHtmlTags` remains available as a lower-level parser option. `htmlComponents` can also handle tags listed in `customHtmlTags`, but it still receives sanitized HTML attributes and `children`; only `streamingComponents` receives `NodeComponentProps`. `setCustomComponents` and `customId` remain supported for compatibility, shared application-level registration, and existing node overrides. If the same normalized tag appears in both `streamingComponents` and `htmlComponents`, `streamingComponents` wins and a development warning is emitted once.
216
+
217
+
This API split fixes discoverability and typing around the two component contracts. HTML safety is still handled by `htmlPolicy` and the existing sanitization rules; the split is not a security boundary.
Copy file name to clipboardExpand all lines: docs/guide/react-markdown-migration.md
+59-17Lines changed: 59 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -67,34 +67,77 @@ This means `components.h1` does not become `components.h1` again. It usually bec
67
67
|`react-markdown`|`markstream-react`| Notes |
68
68
|---|---|---|
69
69
|`children`|`content`| Pass the Markdown string through `content`. |
70
-
|`components`|`setCustomComponents(id?, mapping)`|`react-markdown` keys are HTML tags; `markstream-react` keys are node types such as `heading`, `link`, `paragraph`, `image`, `code_block`, `inline_code`. |
70
+
|`components`|`streamingComponents`, `htmlComponents`, or `setCustomComponents(id?, mapping)`|For HTML-like custom tags, prefer renderer-local maps. Use `streamingComponents` for parser-backed `NodeComponentProps`; use `htmlComponents` for sanitized HTML attributes and `children`. Legacy node overrides still use `setCustomComponents`. |
71
71
|`remarkPlugins`|`customMarkdownIt`| Use `markdown-it` plugins instead of `remark` plugins. Many common Markdown features already work without extra plugins. |
72
72
|`remarkPlugins={[remarkGfm]}`| Often removable | Tables, task checkboxes, strikethrough, code fences, and other common constructs are already supported by the parser. Re-check edge cases before deleting plugin code. |
73
73
|`rehypePlugins`| No direct equivalent | There is no public `rehype` stage. Use custom node renderers, `customHtmlTags`, `parseOptions`, or post-process `nodes` instead. |
74
-
|`rehypeRaw`| Usually not needed | HTML-like tags are already parsed. For custom tags, prefer `customHtmlTags={['thinking']}` plus `setCustomComponents`. |
74
+
|`rehypeRaw`| Usually not needed | HTML-like tags are already parsed. For custom tags, prefer `streamingComponents` or `htmlComponents` depending on the prop contract you need. |
75
75
|`skipHtml`| No direct prop | HTML nodes render by default, with built-in blocking of dangerous attributes/tags and unsafe URLs in the HTML renderers. If you must remove HTML entirely, prefilter the input or parsed nodes yourself. |
76
76
|`allowedElements` / `disallowedElements` / `allowElement`| No direct prop | Filter the parsed `nodes` tree yourself, or replace specific node renderers. |
77
77
|`unwrapDisallowed`| Manual node filtering | Implement this in your node post-processing step if you need it. |
78
78
|`urlTransform`|`parseOptions.validateLink` + custom `link` renderer |`validateLink` is for allow/deny. If you need URL rewriting, do it in a custom `link` renderer or while post-processing nodes. |
79
79
80
80
## Upgrade note: custom HTML-like tags
81
81
82
-
Current `markstream-react` releases no longer infer custom HTML tag names from `setCustomComponents(...)`. If your app renders model output such as `<DocumentLink id="...">title</DocumentLink>` and expects a custom component to receive `props.node`, add the tag to `customHtmlTags`:
82
+
New applications should use renderer-local maps for HTML-like custom tags:
`streamingComponents` selects the parser-backed streaming-node contract. Its keys are added to the parser's effective `customHtmlTags`, so incomplete tags can render with `node.attrs`, `node.content`, and `node.loading`.
108
+
109
+
`htmlComponents` selects the raw/dynamic HTML contract. Components receive sanitized HTML attributes plus `children`, not `props.node`. Attribute values are converted to primitive prop values, but names are preserved from source HTML, so `class` stays `class`.
110
+
111
+
`customHtmlTags` remains available as a lower-level parser option. `setCustomComponents` and `customId` remain supported for compatibility, shared application-level registration, and built-in node overrides.
112
+
113
+
Migration example:
114
+
115
+
```tsx
116
+
// Before
85
117
setCustomComponents('chat', {
86
118
documentlink: DocumentLink,
87
119
})
88
120
89
-
React.createElement(MarkdownRender, {
90
-
customId: 'chat',
91
-
content,
92
-
final: isDone,
93
-
customHtmlTags: ['documentlink'],
94
-
})
121
+
const before = (
122
+
<MarkdownRender
123
+
customId="chat"
124
+
customHtmlTags={['documentlink']}
125
+
content={content}
126
+
/>
127
+
)
128
+
129
+
// After
130
+
const after = (
131
+
<MarkdownRender
132
+
content={content}
133
+
streamingComponents={{
134
+
documentlink: DocumentLink,
135
+
}}
136
+
/>
137
+
)
95
138
```
96
139
97
-
Without `customHtmlTags`, the tag stays on the raw HTML dynamic rendering path. The component can still render, but it receives HTML-style props such as `{ id, children }` instead of `NodeComponentProps`, and it will not receive `node.loading` for streaming partial renders. This auto-inference removal was an undocumented breaking change for apps that relied on the previous behavior.
140
+
Current `markstream-react` releases no longer infer custom HTML tag names from `setCustomComponents(...)`. Without `customHtmlTags` or `streamingComponents`, a complete custom-looking tag stays on the raw HTML dynamic rendering path and receives HTML-style props such as `{ id, children }`. This auto-inference removal was an undocumented breaking change for apps that relied on the previous behavior. The new local maps make that contract explicit in types.
This API split is about discoverability and typing. HTML safety is still handled by `htmlPolicy` and the existing sanitization rules; do not treat the component-map split as a security boundary.
304
+
263
305
## Streaming upgrade path
264
306
265
307
You do not need to adopt streaming on day one. A practical migration path is:
@@ -293,7 +335,7 @@ For most chat streams, the simpler `content` + smooth streaming path is the firs
293
335
294
336
- Replace `<Markdown>{markdown}</Markdown>` with `<MarkdownRender content={markdown} />`.
295
337
- Import `markstream-react/index.css`.
296
-
- Move `components` overrides into `setCustomComponents(customId, mapping)`.
338
+
- Move custom HTML-like tags into `streamingComponents` or `htmlComponents`; keep `setCustomComponents(customId, mapping)` for existing node overrides and shared registrations.
297
339
- Remove plugins that are now redundant before re-adding custom ones.
298
340
- Re-check any `rehype`-based HTML filtering or transformation logic.
299
341
- If your app renders incremental output, evaluate `content` + smooth streaming first; upgrade to `nodes` when you need AST control or external parsing.
Copy file name to clipboardExpand all lines: docs/guide/react-next-ssr.md
+39Lines changed: 39 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,6 +27,43 @@ export default function Page() {
27
27
}
28
28
```
29
29
30
+
Direct component maps contain React component functions, so keep them inside a local Client Component wrapper when you use `markstream-react/next` from an App Router Server Component. Server Components should only pass serializable props such as `content` into that wrapper, matching the [Next.js Client Component prop rules](https://nextjs.org/docs/app/api-reference/directives/use-client).
## Pure Server Rendering with `markstream-react/server`
31
68
32
69
```tsx
@@ -41,6 +78,8 @@ export default function Page() {
41
78
}
42
79
```
43
80
81
+
The pure server entry does not create a Client Component boundary, so server files can pass `streamingComponents` and `htmlComponents` directly when the render should remain server-only.
82
+
44
83
## Custom Components and Custom Tags
45
84
46
85
If you register overrides from a server file, prefer the server helpers so you avoid importing the root package side effects:
0 commit comments