AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Empty `UseServerInsertedHTML` Results Still Trigger A React Render For Each Streamed Chunk

In Next.js App Router, when a `useServerInsertedHTML` callback returns an empty Fragment (or null, empty array), the server still performs an additional React render for each streamed chunk because the callback result is always wrapped in a keyed Fragment, preventing the existing empty-result optimization in `getServerInsertedHTML()`. This leads to significant performance overhead in streaming scenarios with many Suspense boundaries.

highConfidence 92%Next.jsAffected V16.3.6Affected V16.4.0-Canary.48Affected V16.4.0-Canary.50

Origin Analysis

The function `renderServerInsertedHTML()` wraps every callback result in a keyed React Fragment without checking if the result is actually empty (null, undefined, empty array, empty Fragment, etc.). This makes the collected array non-empty, so `getServerInsertedHTML()` never takes its fast path for empty results, causing a Fizz render and extra event-loop turns for each streamed chunk even when no server-inserted HTML is produced.
1. Clone the reproduction repo: `git clone --branch repro/server-inserted-html-skip --single-branch https://github.com/iamibi/next.js.git` 2. Install dependencies and build: `cd next.js && npm install && npx next build` 3. Start the server with OTel verbose: `NEXT_OTEL_VERBOSE=1 npx next start` 4. Request routes with and without the registry and different boundary counts: `curl -s -o /dev/null 'http://localhost:3000/without-registry?boundaries=5'` `curl -s -o /dev/null 'http://localhost:3000/with-registry?boundaries=5'` `curl -s -o /dev/null 'http://localhost:3000/with-registry?boundaries=20'` 5. Observe span counts: without registry produces 1 server_inserted_html_render; with registry produces 14 for 5 boundaries and 44 for 20. The HTML still contains the style exactly once.

Fixing Code Block

Edge Case Audit

The filtering must be strictly limited to statically empty nodes to avoid removing components that might have side effects or conditionally render content. Ensure that the check for empty Fragment only considers direct children and does not attempt to evaluate dynamic content. If any regression occurs (e.g., missing styles or unexpected rendering), revert this change by removing the filter. This fix should be tested with various CSS-in-JS libraries and edge cases where callbacks return nested fragments or arrays. The change should be safe for concurrency and streaming, but monitor memory and performance to confirm no new overhead is introduced.

Ecosystem Topology