Skip to content

[Flight] Pre-walk rendered model instead of using JSON.stringify replacer#36181

Open
unstubbable wants to merge 1 commit intofacebook:mainfrom
unstubbable:resolve-model
Open

[Flight] Pre-walk rendered model instead of using JSON.stringify replacer#36181
unstubbable wants to merge 1 commit intofacebook:mainfrom
unstubbable:resolve-model

Conversation

@unstubbable
Copy link
Copy Markdown
Collaborator

This PR replaces the JSON.stringify replacer callback (task.toJSON) with a two-step approach: a recursive resolveModel() pre-walk followed by a plain JSON.stringify() call with no replacer. This is the server-side counterpart to the client-side JSON.parse reviver removal in #35776.

Based on the approach from #36053 by @mhart.

Problem

When serializing a Flight chunk, emitChunk currently calls JSON.stringify(value, task.toJSON). The task.toJSON replacer is called for every key-value pair in the serialized JSON. While the logic inside the replacer is lightweight, the C++ to JavaScript boundary crossing on every node adds up — V8's JSON.stringify is implemented in C++, and calling back into JavaScript for every property incurs overhead that scales with the number of keys in the output.

Change

Replace the replacer with a two-step process:

  1. resolveModel() recursively walks the rendered value, calling renderModel() on each child — doing the same transformation the replacer used to do, but entirely in JavaScript without C++ boundary crossings.
  2. JSON.stringify() is called with no replacer, staying entirely in C++.

The resolveModel walk also replicates JSON.stringify's toJSON semantics for Date objects.

Results

Measured using the Flight SSR benchmark fixture (#36180) on a dashboard app with ~25 components, 200 product rows (~325KB Flight payload). Tested across Node 20, 22, and 24.

  • bench:bare (in-process, no script injection): Flight+Fizz sync median improves by ~4-5% consistently across all three Node versions.
  • bench:server (HTTP, c=1): Flight+Fizz sync throughput improves by ~3-6% across Node versions. Async results vary between runs but trend positive.

Future opportunity

While the immediate performance improvement is moderate, this change also sets up a potential future optimization: a Flight mode that renders to an object instead of a stream (#36143 (comment)). Since resolveModel() already produces a plain JS object tree before JSON.stringify is called, this intermediate representation could potentially be passed to the SSR client without the serialization-deserialization roundtrip that the current stream-based approach requires.

Co-authored-by: Michael Hart <mhart@cloudflare.com>
@meta-cla meta-cla bot added the CLA Signed label Apr 1, 2026
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Apr 1, 2026
@unstubbable unstubbable changed the title [Flight] Walk serialized JSON instead of using replacer for Flight serialization [Flight] Pre-walk rendered model instead of using JSON.stringify replacer Apr 1, 2026
@react-sizebot
Copy link
Copy Markdown

Comparing: 80b1cab...54b2dff

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 612.91 kB 612.91 kB = 108.30 kB 108.30 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 678.85 kB 678.85 kB = 119.27 kB 119.27 kB
facebook-www/ReactDOM-prod.classic.js = 698.24 kB 698.24 kB = 122.65 kB 122.65 kB
facebook-www/ReactDOM-prod.modern.js = 688.55 kB 688.55 kB = 121.03 kB 121.03 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server/cjs/react-server-flight.production.js +1.81% 66.24 kB 67.44 kB +1.53% 13.08 kB 13.28 kB
oss-stable/react-server/cjs/react-server-flight.production.js +1.81% 66.24 kB 67.44 kB +1.53% 13.08 kB 13.28 kB
oss-experimental/react-server/cjs/react-server-flight.production.js +1.76% 68.05 kB 69.24 kB +1.40% 13.49 kB 13.68 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.96% 110.50 kB 111.57 kB +0.77% 22.31 kB 22.49 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.96% 110.50 kB 111.57 kB +0.77% 22.31 kB 22.49 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +0.95% 111.66 kB 112.73 kB +0.84% 22.58 kB 22.77 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +0.95% 111.66 kB 112.73 kB +0.84% 22.58 kB 22.77 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.95% 112.30 kB 113.36 kB +0.79% 22.68 kB 22.86 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +0.94% 113.46 kB 114.52 kB +0.81% 22.97 kB 23.16 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +0.93% 114.45 kB 115.51 kB +0.75% 23.12 kB 23.29 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +0.93% 114.45 kB 115.51 kB +0.75% 23.12 kB 23.29 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +0.91% 116.24 kB 117.31 kB +0.80% 23.49 kB 23.67 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +0.90% 117.35 kB 118.42 kB +0.69% 23.43 kB 23.60 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +0.90% 117.35 kB 118.42 kB +0.69% 23.43 kB 23.60 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +0.90% 117.71 kB 118.77 kB +0.69% 23.53 kB 23.69 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +0.90% 117.71 kB 118.77 kB +0.69% 23.53 kB 23.69 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.90% 118.44 kB 119.50 kB +0.71% 23.66 kB 23.82 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.90% 118.44 kB 119.50 kB +0.71% 23.66 kB 23.82 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +0.90% 118.59 kB 119.66 kB +0.73% 23.74 kB 23.92 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +0.90% 118.59 kB 119.66 kB +0.73% 23.74 kB 23.92 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +0.90% 118.60 kB 119.66 kB +0.74% 23.74 kB 23.92 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +0.90% 118.60 kB 119.66 kB +0.74% 23.74 kB 23.92 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +0.89% 119.15 kB 120.21 kB +0.73% 23.81 kB 23.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +0.89% 119.50 kB 120.56 kB +0.74% 23.90 kB 24.08 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.88% 120.23 kB 121.30 kB +0.80% 24.01 kB 24.20 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +0.88% 120.39 kB 121.45 kB +0.78% 24.11 kB 24.30 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +0.88% 120.39 kB 121.45 kB +0.77% 24.11 kB 24.30 kB
oss-stable-semver/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.production.js +0.85% 124.31 kB 125.37 kB +0.69% 24.59 kB 24.76 kB
oss-stable/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.production.js +0.85% 124.31 kB 125.37 kB +0.69% 24.59 kB 24.76 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +0.85% 125.36 kB 126.42 kB +0.71% 24.82 kB 25.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +0.85% 125.36 kB 126.42 kB +0.71% 24.82 kB 25.00 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +0.85% 125.37 kB 126.43 kB +0.70% 24.82 kB 25.00 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +0.85% 125.37 kB 126.43 kB +0.70% 24.82 kB 25.00 kB
oss-experimental/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.production.js +0.84% 126.10 kB 127.17 kB +0.71% 24.96 kB 25.14 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +0.84% 127.15 kB 128.21 kB +0.72% 25.19 kB 25.37 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +0.84% 127.16 kB 128.23 kB +0.71% 25.19 kB 25.37 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +0.76% 146.39 kB 147.51 kB +0.88% 26.23 kB 26.46 kB
oss-stable/react-server/cjs/react-server-flight.development.js +0.76% 146.39 kB 147.51 kB +0.88% 26.23 kB 26.46 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +0.75% 148.48 kB 149.59 kB +0.84% 26.69 kB 26.92 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.54% 207.54 kB 208.66 kB +0.53% 37.55 kB 37.75 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.54% 207.54 kB 208.66 kB +0.53% 37.55 kB 37.75 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.53% 209.64 kB 210.76 kB +0.51% 37.97 kB 38.17 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.53% 211.23 kB 212.35 kB +0.53% 38.07 kB 38.27 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.53% 211.23 kB 212.35 kB +0.53% 38.07 kB 38.27 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.52% 213.32 kB 214.44 kB +0.50% 38.49 kB 38.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.52% 215.15 kB 216.27 kB +0.53% 38.84 kB 39.05 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.52% 215.15 kB 216.27 kB +0.53% 38.84 kB 39.05 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.52% 215.62 kB 216.74 kB +0.52% 38.95 kB 39.15 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.52% 215.62 kB 216.74 kB +0.52% 38.95 kB 39.15 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.52% 217.25 kB 218.37 kB +0.51% 39.27 kB 39.47 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.51% 217.73 kB 218.85 kB +0.50% 39.38 kB 39.58 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.51% 218.95 kB 220.07 kB +0.52% 39.36 kB 39.57 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.51% 218.95 kB 220.07 kB +0.52% 39.36 kB 39.57 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.51% 218.95 kB 220.07 kB +0.51% 39.37 kB 39.57 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.51% 218.95 kB 220.07 kB +0.51% 39.37 kB 39.57 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.51% 221.04 kB 222.16 kB +0.50% 39.79 kB 39.99 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.51% 221.04 kB 222.16 kB +0.51% 39.79 kB 39.99 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.48% 234.91 kB 236.03 kB +0.45% 42.61 kB 42.80 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.48% 234.91 kB 236.03 kB +0.45% 42.61 kB 42.80 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.47% 237.00 kB 238.12 kB +0.42% 43.03 kB 43.21 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.46% 241.59 kB 242.71 kB +0.45% 43.19 kB 43.39 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.46% 241.59 kB 242.71 kB +0.45% 43.19 kB 43.39 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.46% 243.68 kB 244.80 kB +0.42% 43.62 kB 43.80 kB
oss-stable-semver/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.development.js +0.45% 248.11 kB 249.23 kB +0.44% 44.26 kB 44.45 kB
oss-stable/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.development.js +0.45% 248.11 kB 249.23 kB +0.44% 44.26 kB 44.45 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.45% 249.31 kB 250.43 kB +0.41% 44.58 kB 44.77 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.45% 249.31 kB 250.43 kB +0.41% 44.58 kB 44.77 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.45% 249.32 kB 250.44 kB +0.41% 44.59 kB 44.78 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.45% 249.32 kB 250.44 kB +0.41% 44.59 kB 44.78 kB
oss-experimental/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.development.js +0.45% 250.20 kB 251.32 kB +0.41% 44.69 kB 44.88 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.45% 251.40 kB 252.52 kB +0.41% 45.01 kB 45.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.45% 251.41 kB 252.53 kB +0.41% 45.02 kB 45.20 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +0.30% 359.47 kB 360.53 kB +0.28% 66.69 kB 66.88 kB

Generated by 🚫 dangerJS against 54b2dff

@unstubbable unstubbable marked this pull request as ready for review April 1, 2026 22:08
@unstubbable unstubbable requested a review from gnoff April 1, 2026 22:08
@mhart
Copy link
Copy Markdown

mhart commented Apr 2, 2026

You could've asked me to just pull this out from my PR into a new one – I would've happily done it 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants