Skip to content

[Fizz] Batch Suspense Boundary Reveal with Throttle #33076

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 5 commits into from
May 1, 2025

Conversation

sebmarkbage
Copy link
Collaborator

Stacked on #33073.

React semantics is that Suspense boundaries reveal with a throttle (300ms). That helps avoid flashing reveals when a stream reveals many individual steps back to back. It can also improve overall performance by batching the layout and paint work that has to happen at each step.

Unfortunately we never implemented this for SSR streaming - only for client navigations. This is highly noticeable on very dynamic sites with lots of Suspense boundaries. It can look good with a client nav but feel glitchy when you reload the page or initial load.

This fixes the Fizz runtime to be throttled and reveals batched into a single paint at a time. We do this by first tracking the last paint after the complete (this will be the first paint if rel="expect" is respected). Then in the completeBoundary operation we queue the operation and then flush it all into a throttled batch.

Another motivation is that View Transitions need to operate as a batch and individual steps get queued in a sequence so it's extra important to include as much content as possible in each animated step. This will be done in a follow up for SSR View Transitions.

@sebmarkbage sebmarkbage requested a review from gnoff May 1, 2025 02:51
@sebmarkbage sebmarkbage requested a review from eps1lon May 1, 2025 02:52
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label May 1, 2025
@react-sizebot
Copy link

react-sizebot commented May 1, 2025

Comparing: ee077b6...9d727c5

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.05% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 527.81 kB 527.81 kB = 93.08 kB 93.08 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 633.44 kB 633.44 kB = 111.27 kB 111.27 kB
facebook-www/ReactDOM-prod.classic.js = 671.22 kB 671.22 kB = 117.71 kB 117.71 kB
facebook-www/ReactDOM-prod.modern.js = 661.50 kB 661.50 kB = 116.15 kB 116.15 kB
oss-experimental/react-dom/unstable_server-external-runtime.js +10.19% 8.50 kB 9.36 kB +10.84% 2.26 kB 2.51 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-dom/unstable_server-external-runtime.js +10.19% 8.50 kB 9.36 kB +10.84% 2.26 kB 2.51 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.72% 213.54 kB 215.08 kB +0.72% 38.92 kB 39.20 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.72% 213.57 kB 215.10 kB +0.72% 38.94 kB 39.22 kB
facebook-www/ReactDOMServer-prod.modern.js +0.72% 221.79 kB 223.38 kB +0.67% 40.02 kB 40.29 kB
facebook-www/ReactDOMServer-prod.classic.js +0.71% 224.47 kB 226.07 kB +0.66% 40.35 kB 40.62 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js +0.70% 218.06 kB 219.59 kB +0.65% 40.68 kB 40.94 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js +0.70% 218.08 kB 219.62 kB +0.65% 40.70 kB 40.97 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.69% 232.56 kB 234.15 kB +0.64% 41.58 kB 41.85 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +0.68% 233.27 kB 234.86 kB +0.76% 41.83 kB 42.15 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +0.68% 233.35 kB 234.94 kB +0.76% 41.85 kB 42.17 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +0.68% 226.07 kB 227.60 kB +0.72% 41.58 kB 41.88 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.js +0.67% 237.64 kB 239.23 kB +0.60% 43.48 kB 43.74 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js +0.67% 238.48 kB 240.07 kB +0.73% 43.76 kB 44.08 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js +0.67% 238.55 kB 240.14 kB +0.73% 43.79 kB 44.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js +0.66% 218.48 kB 219.93 kB +0.62% 40.23 kB 40.48 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js +0.66% 218.55 kB 220.00 kB +0.62% 40.26 kB 40.51 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js +0.66% 235.12 kB 236.68 kB +0.71% 42.81 kB 43.12 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js +0.66% 235.20 kB 236.75 kB +0.72% 42.84 kB 43.14 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +0.63% 260.92 kB 262.57 kB +0.68% 45.26 kB 45.57 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +0.63% 238.96 kB 240.47 kB +0.57% 43.01 kB 43.26 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +0.62% 266.87 kB 268.52 kB +0.68% 47.38 kB 47.70 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +0.61% 262.87 kB 264.49 kB +0.61% 46.43 kB 46.71 kB
facebook-www/ReactDOMServer-dev.modern.js +0.55% 377.80 kB 379.87 kB +0.52% 67.85 kB 68.20 kB
facebook-www/ReactDOMServer-dev.classic.js +0.54% 381.26 kB 383.33 kB +0.51% 68.38 kB 68.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js +0.54% 365.02 kB 366.99 kB +0.47% 66.33 kB 66.64 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.54% 365.02 kB 366.99 kB +0.47% 66.33 kB 66.64 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js +0.54% 365.05 kB 367.01 kB +0.47% 66.35 kB 66.67 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.54% 365.05 kB 367.02 kB +0.47% 66.35 kB 66.67 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +0.53% 391.72 kB 393.79 kB +0.49% 69.60 kB 69.94 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.53% 391.73 kB 393.80 kB +0.49% 69.60 kB 69.94 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js +0.51% 325.68 kB 327.36 kB +0.48% 63.38 kB 63.68 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js +0.51% 325.76 kB 327.43 kB +0.48% 63.40 kB 63.71 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +0.50% 350.47 kB 352.22 kB +0.49% 66.70 kB 67.02 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js +0.50% 382.07 kB 383.96 kB +0.43% 68.72 kB 69.02 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js +0.50% 382.14 kB 384.04 kB +0.43% 68.77 kB 69.07 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +0.50% 382.85 kB 384.74 kB +0.42% 68.87 kB 69.16 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +0.50% 382.92 kB 384.82 kB +0.43% 68.92 kB 69.21 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +0.49% 378.61 kB 380.46 kB +0.40% 68.10 kB 68.37 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +0.49% 378.68 kB 380.53 kB +0.41% 68.15 kB 68.42 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +0.48% 418.05 kB 420.05 kB +0.41% 72.77 kB 73.07 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +0.48% 419.06 kB 421.05 kB +0.41% 72.99 kB 73.29 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +0.47% 414.06 kB 416.01 kB +0.40% 72.18 kB 72.47 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +0.47% 369.17 kB 370.90 kB +0.40% 66.61 kB 66.88 kB
oss-stable-semver/react-server/cjs/react-server.production.js +0.26% 117.36 kB 117.67 kB +0.14% 20.92 kB 20.95 kB
oss-stable/react-server/cjs/react-server.production.js +0.26% 117.36 kB 117.67 kB +0.14% 20.92 kB 20.95 kB
oss-experimental/react-server/cjs/react-server.production.js +0.24% 131.43 kB 131.73 kB +0.11% 22.80 kB 22.83 kB

Generated by 🚫 dangerJS against 9d727c5

@sebmarkbage
Copy link
Collaborator Author

sebmarkbage commented May 1, 2025

One thing that needs to be resolved is how to deal with #31620 because it assumes that these are synchronously evaluated before the content loads.

I think that solution has an existing problem though because a stylesheet can also block reveal, which will allow client rendering. That commit is likely itself blocked on the same stylesheet loading though. Once it commits it'll replace the SSR content that has also already streamed in which it really shouldn't ideally and may have consequences for View Transitions.

Follow up here #33087.

@@ -5217,10 +5217,19 @@ function flushCompletedQueues(
);
flushSegment(request, destination, completedRootSegment, null);
request.completedRootSegment = null;
const isComplete =
request.allPendingTasks === 0 &&
request.pingedTasks.length === 0 &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove here and in finally of flushCompletedQueues

@sebmarkbage sebmarkbage merged commit ee7fee8 into facebook:main May 1, 2025
239 checks passed
sebmarkbage added a commit that referenced this pull request May 1, 2025
Stacked on #33076.

This fixes a bug where we used the "complete" status but the
DOMContentLoaded event. This checks for not "loading" instead.

We also add a new status where the boundary has been marked as complete
by the server but has not yet flushed either due to being throttled,
suspended on CSS or animating.
github-actions bot pushed a commit that referenced this pull request May 1, 2025
Stacked on #33073.

React semantics is that Suspense boundaries reveal with a throttle
(300ms). That helps avoid flashing reveals when a stream reveals many
individual steps back to back. It can also improve overall performance
by batching the layout and paint work that has to happen at each step.

Unfortunately we never implemented this for SSR streaming - only for
client navigations. This is highly noticeable on very dynamic sites with
lots of Suspense boundaries. It can look good with a client nav but feel
glitchy when you reload the page or initial load.

This fixes the Fizz runtime to be throttled and reveals batched into a
single paint at a time. We do this by first tracking the last paint
after the complete (this will be the first paint if `rel="expect"` is
respected). Then in the `completeBoundary` operation we queue the
operation and then flush it all into a throttled batch.

Another motivation is that View Transitions need to operate as a batch
and individual steps get queued in a sequence so it's extra important to
include as much content as possible in each animated step. This will be
done in a follow up for SSR View Transitions.

DiffTrain build for [ee7fee8](ee7fee8)
github-actions bot pushed a commit that referenced this pull request May 1, 2025
Stacked on #33076.

This fixes a bug where we used the "complete" status but the
DOMContentLoaded event. This checks for not "loading" instead.

We also add a new status where the boundary has been marked as complete
by the server but has not yet flushed either due to being throttled,
suspended on CSS or animating.

DiffTrain build for [0ed6ceb](0ed6ceb)
github-actions bot pushed a commit that referenced this pull request May 1, 2025
Stacked on #33076.

This fixes a bug where we used the "complete" status but the
DOMContentLoaded event. This checks for not "loading" instead.

We also add a new status where the boundary has been marked as complete
by the server but has not yet flushed either due to being throttled,
suspended on CSS or animating.

DiffTrain build for [0ed6ceb](0ed6ceb)
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