Skip to content

Add lazy reasons to browser() - #37241

Merged
gnoff merged 1 commit into
react:mainfrom
gnoff:jstory/browser-reason
Aug 10, 2026
Merged

Add lazy reasons to browser()#37241
gnoff merged 1 commit into
react:mainfrom
gnoff:jstory/browser-reason

Conversation

@gnoff

@gnoff gnoff commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Changes ReactDOM.browser() to return a cheap branded recoverable token instead of eagerly constructing an Error. It accepts an optional reason string or initializer that runs only when a server renderer consumes the token and may return any value; the client renderer ignores the reason without invoking the initializer, so browser-only rendering does not pay for an unused stack.

When Fizz consumes the token through use() or abort(), it creates a consistent browser-bailout error at the consumption point so its stack identifies the relevant operation. The initialized reason is preserved unchanged as the optional cause, allowing strings, errors, and structured framework metadata without runtime validation. If an initializer throws, Fizz substitutes a stable diagnostic fallback so reason generation cannot change rendering control flow. Successful recoveries report the error through onBrowserBailout.

When no Suspense boundary can recover the render, Fizz clones the branded recoverable error into an unbranded fatal diagnostic while preserving its cause and consumption frames. During an abort, the request retains the original branded error so every remaining task observes the same reason; fatal clones are created only when reporting a fatal root or closing the stream. Centralized recoverable logging uses the brand to route successful bailouts through onBrowserBailout and fatal clones through onError. The empty recoverable digest and client hydration suppression behavior remain unchanged.

Tests cover omitted and direct reasons, lazy string, error, structured, and primitive reasons, repeated use sites, throwing initializers, lazy client behavior, consumption stacks, flattened fatal errors, recoverable and fatal use and abort paths, nested aborts, direct throws, debug tools, and development and production rendering.

@meta-cla meta-cla Bot added the CLA Signed label Aug 7, 2026
@github-actions github-actions Bot added the React Core Team Opened by a member of the React Core Team label Aug 7, 2026
@react-sizebot

react-sizebot commented Aug 7, 2026

Copy link
Copy Markdown

Comparing: 2042572...db48e6e

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 = 7.45 kB 7.35 kB = 2.00 kB 1.96 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 617.53 kB 617.53 kB = 109.41 kB 109.41 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 7.46 kB 7.36 kB = 2.00 kB 1.96 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 688.75 kB 688.75 kB = 120.85 kB 120.85 kB
facebook-www/ReactDOM-prod.classic.js = 709.04 kB 708.94 kB = 124.37 kB 124.34 kB
facebook-www/ReactDOM-prod.modern.js = 699.35 kB 699.26 kB = 122.79 kB 122.76 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-dom/cjs/react-dom-server-legacy.browser.production.js +0.30% 257.76 kB 258.54 kB +0.52% 46.31 kB 46.55 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.30% 257.79 kB 258.57 kB +0.52% 46.33 kB 46.57 kB
oss-stable-semver/react-server/cjs/react-server.production.js +0.30% 154.72 kB 155.18 kB +0.72% 27.05 kB 27.25 kB
oss-stable/react-server/cjs/react-server.production.js +0.30% 154.72 kB 155.18 kB +0.72% 27.05 kB 27.25 kB
facebook-www/ReactDOMServer-prod.modern.js +0.29% 264.90 kB 265.68 kB +0.50% 47.19 kB 47.43 kB
facebook-www/ReactDOMServer-prod.classic.js +0.29% 267.23 kB 268.01 kB +0.50% 47.56 kB 47.79 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.29% 267.88 kB 268.66 kB +0.50% 47.81 kB 48.05 kB
oss-experimental/react-server/cjs/react-server.production.js +0.29% 158.55 kB 159.01 kB +0.71% 27.86 kB 28.05 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +0.27% 285.96 kB 286.74 kB +0.44% 50.78 kB 51.01 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +0.27% 286.04 kB 286.82 kB +0.45% 50.81 kB 51.04 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +0.26% 299.10 kB 299.88 kB +0.48% 52.69 kB 52.94 kB
oss-experimental/react-debug-tools/cjs/react-debug-tools.production.js = 30.90 kB 30.80 kB = 5.97 kB 5.94 kB
oss-stable-semver/react-debug-tools/cjs/react-debug-tools.production.js = 30.90 kB 30.80 kB = 5.97 kB 5.94 kB
oss-stable/react-debug-tools/cjs/react-debug-tools.production.js = 30.90 kB 30.80 kB = 5.97 kB 5.94 kB
oss-experimental/react-dom/cjs/react-dom.development.js = 18.67 kB 18.51 kB = 4.04 kB 3.99 kB
oss-stable/react-dom/cjs/react-dom.development.js = 18.66 kB 18.51 kB = 4.04 kB 3.98 kB
oss-stable-semver/react-dom/cjs/react-dom.development.js = 18.64 kB 18.48 kB = 4.00 kB 3.96 kB
facebook-react-native/react-dom/cjs/ReactDOM-dev.js = 17.70 kB 17.55 kB = 3.80 kB 3.75 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 7.46 kB 7.36 kB = 2.00 kB 1.96 kB
oss-stable/react-dom/cjs/react-dom.production.js = 7.45 kB 7.35 kB = 2.00 kB 1.96 kB
oss-stable-semver/react-dom/cjs/react-dom.production.js = 7.43 kB 7.33 kB = 1.97 kB 1.94 kB
facebook-react-native/react-dom/cjs/ReactDOM-prod.js = 7.25 kB 7.15 kB = 1.97 kB 1.94 kB
facebook-react-native/react-dom/cjs/ReactDOM-profiling.js = 7.25 kB 7.15 kB = 1.97 kB 1.94 kB

Generated by 🚫 dangerJS against db48e6e

@gnoff
gnoff requested a review from eps1lon August 7, 2026 21:51
@gnoff
gnoff force-pushed the jstory/browser-reason branch 2 times, most recently from 42a4856 to d199178 Compare August 7, 2026 22:16
@gnoff
gnoff force-pushed the jstory/browser-reason branch 4 times, most recently from 7b8c048 to d56e1cc Compare August 8, 2026 06:03
Changes `ReactDOM.browser()` to return a cheap branded recoverable token instead of eagerly constructing an `Error`. It accepts an optional reason string or initializer that runs only when a server renderer consumes the token and may return any value; the client renderer ignores the reason without invoking the initializer, so browser-only rendering does not pay for an unused stack.

When Fizz consumes the token through `use()` or `abort()`, it creates a consistent browser-bailout error at the consumption point so its stack identifies the relevant operation. The initialized reason is preserved unchanged as the optional `cause`, allowing strings, errors, and structured framework metadata without runtime validation. If an initializer throws, Fizz substitutes a stable diagnostic fallback so reason generation cannot change rendering control flow. Successful recoveries report the error through `onBrowserBailout`.

When no Suspense boundary can recover the render, Fizz clones the branded recoverable error into an unbranded fatal diagnostic while preserving its cause and consumption frames. During an abort, the request retains the original branded error so every remaining task observes the same reason; fatal clones are created only when reporting a fatal root or closing the stream. Centralized recoverable logging uses the brand to route successful bailouts through `onBrowserBailout` and fatal clones through `onError`. The empty recoverable digest and client hydration suppression behavior remain unchanged.

Tests cover omitted and direct reasons, lazy string, error, structured, and primitive reasons, repeated use sites, throwing initializers, lazy client behavior, consumption stacks, flattened fatal errors, recoverable and fatal use and abort paths, nested aborts, direct throws, debug tools, and development and production rendering.
@gnoff
gnoff force-pushed the jstory/browser-reason branch from d56e1cc to db48e6e Compare August 8, 2026 15:07
@gnoff
gnoff merged commit 807d21f into react:main Aug 10, 2026
238 checks passed
@gnoff
gnoff deleted the jstory/browser-reason branch August 10, 2026 15:42
github-actions Bot pushed a commit that referenced this pull request Aug 10, 2026
Changes `ReactDOM.browser()` to return a cheap branded recoverable token
instead of eagerly constructing an `Error`. It accepts an optional
reason string or initializer that runs only when a server renderer
consumes the token and may return any value; the client renderer ignores
the reason without invoking the initializer, so browser-only rendering
does not pay for an unused stack.

When Fizz consumes the token through `use()` or `abort()`, it creates a
consistent browser-bailout error at the consumption point so its stack
identifies the relevant operation. The initialized reason is preserved
unchanged as the optional `cause`, allowing strings, errors, and
structured framework metadata without runtime validation. If an
initializer throws, Fizz substitutes a stable diagnostic fallback so
reason generation cannot change rendering control flow. Successful
recoveries report the error through `onBrowserBailout`.

When no Suspense boundary can recover the render, Fizz clones the
branded recoverable error into an unbranded fatal diagnostic while
preserving its cause and consumption frames. During an abort, the
request retains the original branded error so every remaining task
observes the same reason; fatal clones are created only when reporting a
fatal root or closing the stream. Centralized recoverable logging uses
the brand to route successful bailouts through `onBrowserBailout` and
fatal clones through `onError`. The empty recoverable digest and client
hydration suppression behavior remain unchanged.

Tests cover omitted and direct reasons, lazy string, error, structured,
and primitive reasons, repeated use sites, throwing initializers, lazy
client behavior, consumption stacks, flattened fatal errors, recoverable
and fatal use and abort paths, nested aborts, direct throws, debug
tools, and development and production rendering.

DiffTrain build for [807d21f](807d21f)
github-actions Bot pushed a commit that referenced this pull request Aug 10, 2026
Changes `ReactDOM.browser()` to return a cheap branded recoverable token
instead of eagerly constructing an `Error`. It accepts an optional
reason string or initializer that runs only when a server renderer
consumes the token and may return any value; the client renderer ignores
the reason without invoking the initializer, so browser-only rendering
does not pay for an unused stack.

When Fizz consumes the token through `use()` or `abort()`, it creates a
consistent browser-bailout error at the consumption point so its stack
identifies the relevant operation. The initialized reason is preserved
unchanged as the optional `cause`, allowing strings, errors, and
structured framework metadata without runtime validation. If an
initializer throws, Fizz substitutes a stable diagnostic fallback so
reason generation cannot change rendering control flow. Successful
recoveries report the error through `onBrowserBailout`.

When no Suspense boundary can recover the render, Fizz clones the
branded recoverable error into an unbranded fatal diagnostic while
preserving its cause and consumption frames. During an abort, the
request retains the original branded error so every remaining task
observes the same reason; fatal clones are created only when reporting a
fatal root or closing the stream. Centralized recoverable logging uses
the brand to route successful bailouts through `onBrowserBailout` and
fatal clones through `onError`. The empty recoverable digest and client
hydration suppression behavior remain unchanged.

Tests cover omitted and direct reasons, lazy string, error, structured,
and primitive reasons, repeated use sites, throwing initializers, lazy
client behavior, consumption stacks, flattened fatal errors, recoverable
and fatal use and abort paths, nested aborts, direct throws, debug
tools, and development and production rendering.

DiffTrain build for [807d21f](807d21f)
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