Rendering markdown in an AI chat UI looks simple until you hit code blocks, math, and a user resizing their window mid-stream.
Every getBoundingClientRect() call while tokens arrive forces a synchronous layout pass.
// Naive approach — reflow per token
element.textContent = nextChunk;
const height = element.getBoundingClientRect().height;
| Renderer | Streaming | Plugins | Reflow cost |
|---|---|---|---|
| react-markdown | Re-parses per token | Wire yourself | Full DOM reflow |
| streamdown | Incremental blocks | Built-in | Reflow per block |
| inkset | Measured arithmetic | Built-in | O(1) arithmetic |
The cost drops from:
tresize≈treflow+tmeasure+tpatch+tpaint
to:
tresize≈tarithmetic+tpaint
Rendering markdown in an AI chat UI looks simple until you hit code blocks, math, and a user resizing their window mid-stream.
Every getBoundingClientRect() call while tokens arrive forces a synchronous layout pass.
// Naive approach — reflow per tokenelement.textContent = nextChunk;const height = element.getBoundingClientRect().height;| Renderer | Streaming | Plugins | Reflow cost |
|---|---|---|---|
| react-markdown | Re-parses per token | Wire yourself | Full DOM reflow |
| streamdown | Incremental blocks | Built-in | Reflow per block |
| inkset | Measured arithmetic | Built-in | O(1) arithmetic |
The cost drops from:
tresize≈treflow+tmeasure+tpatch+tpaint
to:
tresize≈tarithmetic+tpaint
// Naive approach — reflow per tokenelement.textContent = nextChunk;const height = element.getBoundingClientRect().height;