Skip to content

[Fizz] Error and deopt from rel=expect for large documents without boundaries #33454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2025

Conversation

sebmarkbage
Copy link
Collaborator

We want to make sure that we can block the reveal of a well designed complete shell reliably. In the Suspense model, client transitions don't have any way to implicitly resolve. This means you need to use Suspense or SuspenseList to explicitly split the document. Relying on implicit would mean you can't add a Suspense boundary later where needed. So we highly encourage the use of them around large content.

However, if you have constructed a too large shell (e.g. by not adding any Suspense boundaries at all) then that might take too long to render on the client. We shouldn't punish users (or overzealous metrics tracking tools like search engines) in that scenario.

This opts out of render blocking if the shell ends up too large to be intentional and too slow to load. Instead it deopts to showing the content split up in arbitrary ways (browser default). It only does this for SSR, and not client navs so it's not reliable.

In fact, we issue an error to onError. This error is recoverable in that the document is still produced. It's up to your framework to decide if this errors the build or just surface it for action later.

What should be the limit though? There's a trade off here. If this limit is too low then you can't fit a reasonably well built UI within it without getting errors. If it's too high then things that accidentally fall below it might take too long to load.

I came up with 512kB of uncompressed shell HTML. See the comment in code for the rationale for this number. TL;DR: Data and theory indicates that having this much content inside rel="expect" doesn't meaningfully change metrics. Research of above-the-fold content on various websites indicate that this can comfortable fit all of them which should be enough for any intentional initial paint.

@sebmarkbage sebmarkbage requested review from gnoff and eps1lon June 6, 2025 01:02
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Jun 6, 2025
@@ -5888,11 +5930,32 @@ function flushCompletedQueues(

flushedByteSize = request.byteSize; // Start counting bytes
// TODO: Count the size of the preamble chunks too.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO should probably be fixed since this can have meaningful impact on this metric.

@react-sizebot
Copy link

react-sizebot commented Jun 6, 2025

Comparing: dddcae7...be6c468

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.68 kB 6.68 kB +0.11% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 530.07 kB 530.07 kB = 93.57 kB 93.57 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.16% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 651.16 kB 651.16 kB = 114.69 kB 114.70 kB
facebook-www/ReactDOM-prod.classic.js = 676.11 kB 676.11 kB = 118.97 kB 118.97 kB
facebook-www/ReactDOM-prod.modern.js = 666.39 kB 666.39 kB = 117.36 kB 117.36 kB
oss-experimental/react-server-dom-webpack/server.node.unbundled.js = 1.07 kB 0.74 kB = 0.35 kB 0.30 kB
oss-stable-semver/react-server-dom-webpack/server.node.unbundled.js = 1.07 kB 0.74 kB = 0.35 kB 0.30 kB
oss-stable/react-server-dom-webpack/server.node.unbundled.js = 1.07 kB 0.74 kB = 0.35 kB 0.30 kB
oss-experimental/react-server-dom-webpack/client.node.unbundled.js = 1.01 kB 0.27 kB = 0.35 kB 0.17 kB
oss-stable-semver/react-server-dom-webpack/client.node.unbundled.js = 1.01 kB 0.27 kB = 0.35 kB 0.17 kB
oss-stable/react-server-dom-webpack/client.node.unbundled.js = 1.01 kB 0.27 kB = 0.35 kB 0.17 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.development.js Deleted 122.62 kB 0.00 kB Deleted 23.04 kB 0.00 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.production.js Deleted 58.69 kB 0.00 kB Deleted 11.99 kB 0.00 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.development.js Deleted 171.76 kB 0.00 kB Deleted 31.77 kB 0.00 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.production.js Deleted 98.19 kB 0.00 kB Deleted 19.96 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.development.js Deleted 106.96 kB 0.00 kB Deleted 20.14 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.production.js Deleted 58.15 kB 0.00 kB Deleted 11.90 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.development.js Deleted 154.96 kB 0.00 kB Deleted 28.57 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.production.js Deleted 93.59 kB 0.00 kB Deleted 19.18 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.development.js Deleted 106.96 kB 0.00 kB Deleted 20.14 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.production.js Deleted 58.15 kB 0.00 kB Deleted 11.90 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.development.js Deleted 154.96 kB 0.00 kB Deleted 28.57 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.production.js Deleted 93.59 kB 0.00 kB Deleted 19.18 kB 0.00 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-server/cjs/react-server.production.js +0.65% 150.28 kB 151.26 kB +1.17% 25.91 kB 26.22 kB
oss-experimental/react-server/cjs/react-server.development.js +0.51% 210.84 kB 211.92 kB +0.87% 36.51 kB 36.83 kB
oss-experimental/react-markup/cjs/react-markup.production.js +0.34% 246.99 kB 247.83 kB +0.67% 45.07 kB 45.37 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +0.33% 258.49 kB 259.36 kB +0.63% 47.08 kB 47.37 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +0.32% 270.70 kB 271.56 kB +0.64% 48.28 kB 48.58 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +0.30% 291.75 kB 292.62 kB +0.60% 51.72 kB 52.03 kB
oss-experimental/react-dom/cjs/react-dom-server.node-webstreams.production.js +0.30% 292.26 kB 293.12 kB +0.60% 51.43 kB 51.74 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +0.29% 295.78 kB 296.65 kB +0.59% 52.68 kB 52.99 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +0.24% 348.02 kB 348.86 kB +0.46% 64.38 kB 64.68 kB
oss-experimental/react-markup/cjs/react-markup.development.js +0.24% 390.87 kB 391.80 kB +0.41% 70.13 kB 70.41 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +0.23% 379.23 kB 380.12 kB +0.44% 72.35 kB 72.68 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +0.23% 404.22 kB 405.15 kB +0.42% 72.25 kB 72.56 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +0.21% 445.85 kB 446.79 kB +0.39% 77.87 kB 78.17 kB
oss-experimental/react-dom/cjs/react-dom-server.node-webstreams.development.js +0.21% 446.87 kB 447.81 kB +0.40% 77.46 kB 77.77 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +0.21% 449.84 kB 450.78 kB +0.39% 78.52 kB 78.83 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +0.21% 450.84 kB 451.79 kB +0.38% 78.74 kB 79.04 kB
oss-experimental/react-server-dom-webpack/server.node.unbundled.js = 1.07 kB 0.74 kB = 0.35 kB 0.30 kB
oss-stable-semver/react-server-dom-webpack/server.node.unbundled.js = 1.07 kB 0.74 kB = 0.35 kB 0.30 kB
oss-stable/react-server-dom-webpack/server.node.unbundled.js = 1.07 kB 0.74 kB = 0.35 kB 0.30 kB
oss-experimental/react-server-dom-webpack/client.node.unbundled.js = 1.01 kB 0.27 kB = 0.35 kB 0.17 kB
oss-stable-semver/react-server-dom-webpack/client.node.unbundled.js = 1.01 kB 0.27 kB = 0.35 kB 0.17 kB
oss-stable/react-server-dom-webpack/client.node.unbundled.js = 1.01 kB 0.27 kB = 0.35 kB 0.17 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.development.js Deleted 122.62 kB 0.00 kB Deleted 23.04 kB 0.00 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.production.js Deleted 58.69 kB 0.00 kB Deleted 11.99 kB 0.00 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.development.js Deleted 171.76 kB 0.00 kB Deleted 31.77 kB 0.00 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.production.js Deleted 98.19 kB 0.00 kB Deleted 19.96 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.development.js Deleted 106.96 kB 0.00 kB Deleted 20.14 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.production.js Deleted 58.15 kB 0.00 kB Deleted 11.90 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.development.js Deleted 154.96 kB 0.00 kB Deleted 28.57 kB 0.00 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.production.js Deleted 93.59 kB 0.00 kB Deleted 19.18 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.development.js Deleted 106.96 kB 0.00 kB Deleted 20.14 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node-webstreams.unbundled.production.js Deleted 58.15 kB 0.00 kB Deleted 11.90 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.development.js Deleted 154.96 kB 0.00 kB Deleted 28.57 kB 0.00 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node-webstreams.unbundled.production.js Deleted 93.59 kB 0.00 kB Deleted 19.18 kB 0.00 kB

Generated by 🚫 dangerJS against be6c468

(gate(flags => flags.enableFizzBlockingRender)
? '<template id="_R_"></template>'
: ''),
);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disabled in the legacy API since these aren't part of the new "streaming" world. Unnecessary to break. They also won't have opt-ins to things like MPA ViewTransitions which this affects.

@sebmarkbage sebmarkbage merged commit d177272 into facebook:main Jun 6, 2025
468 of 469 checks passed
github-actions bot pushed a commit that referenced this pull request Jun 6, 2025
…undaries (#33454)

We want to make sure that we can block the reveal of a well designed
complete shell reliably. In the Suspense model, client transitions don't
have any way to implicitly resolve. This means you need to use Suspense
or SuspenseList to explicitly split the document. Relying on implicit
would mean you can't add a Suspense boundary later where needed. So we
highly encourage the use of them around large content.

However, if you have constructed a too large shell (e.g. by not adding
any Suspense boundaries at all) then that might take too long to render
on the client. We shouldn't punish users (or overzealous metrics
tracking tools like search engines) in that scenario.

This opts out of render blocking if the shell ends up too large to be
intentional and too slow to load. Instead it deopts to showing the
content split up in arbitrary ways (browser default). It only does this
for SSR, and not client navs so it's not reliable.

In fact, we issue an error to `onError`. This error is recoverable in
that the document is still produced. It's up to your framework to decide
if this errors the build or just surface it for action later.

What should be the limit though? There's a trade off here. If this limit
is too low then you can't fit a reasonably well built UI within it
without getting errors. If it's too high then things that accidentally
fall below it might take too long to load.

I came up with 512kB of uncompressed shell HTML. See the comment in code
for the rationale for this number. TL;DR: Data and theory indicates that
having this much content inside `rel="expect"` doesn't meaningfully
change metrics. Research of above-the-fold content on various websites
indicate that this can comfortable fit all of them which should be
enough for any intentional initial paint.

DiffTrain build for [d177272](d177272)
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.

4 participants