Skip to content

[pull] main from react:main - #624

Merged
pull[bot] merged 4 commits into
code:mainfrom
react:main
Jul 31, 2026
Merged

[pull] main from react:main#624
pull[bot] merged 4 commits into
code:mainfrom
react:main

Conversation

@pull

@pull pull Bot commented Jul 31, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

gnoff and others added 4 commits July 30, 2026 13:15
## Summary

Adds a new API to `react-dom` called `browser()`.

`browser()` returns a "usable" that will error during SSR and resolve
during rendering in the browser. The purpose is to allow you to express
the idea that a component should suspend on the server but not in the
browser. The method is not available inside a `react-server`
environment. This is a client only feature.

This is a `react-dom` API because the concept of browser doesn't apply
generally to React itself.

This codifies a pattern that is common in some apps where you error
during SSR to prevent rendering some component on the server and you end
up suppressing the error that is reported in the client to avoid this
appearing like a problem rather than intended behavior. Unfortunately
this is not an option for many because hacking around to prevent errors
from being logged is not practical for many

By making this a React API we enabled this common pattern in any React
using library or application

```tsx
import {use, Suspense} from 'react';
import {browser} from 'react-dom';

function BrowserOnly() {
  use(browser());
  return <ClientContent />;
}

function App() {
  return (
    <Suspense fallback={<Fallback />}>
      <BrowserOnly />
    </Suspense>
  );
}
```

It is an error to `use(browser())` outside of a Suspense boundary
because you cannot recover from the root. this restriction may be lifted
in the future but is part of the current limitations of the API

## Implementation

Deferring rendering to a downstream system is modeled in React already
as recoverable errors. The idea is that in some environments you might
not want to report something directly as an error because a later
environment has an opportunity to recover from it without alerting the
user to the mishap. This concept also shows up in RSC with halted
references. They can "recover" in a later render by eventually resolving
to some value.

To model the idea of "render in the browser" we are really just modeling
an intentional recoverable error. However since you don't want to treat
this kind of error as exceptional we intentionally suppress logging.
Additionally since aborting a server render is semantically equivalent
to "erroring" in every unfinished task we also support aborting with a
`browser()` so you can describe ending a stream with intentional holes
that won't be logged as errors in the browser when hydrating.

One interesting thing we do with this particular API is it returns an
object that is isomorphic and it's the `use` or `abort` function that
handles differing behaviors. This means you can create these objects in
module scope and use them even in complex scenarios like server
rendering inside the browser while React is rendering.

This implementation is flagged so we can disable the feature quickly if
we decide to not ship this in a stable. It is going into React
unprefixed for now because the semantics are clear and the utility is
widely known.

## Alternatives

We considered `useBrowser()` or a similar hook however this means you
must call it unconditionally. There are use cases where props might
influence whether you want to allow something to render during SSR or
not. for instance you might have a data fetching library that accepts
initial data on the server but if it doesn't receive initial data it
falls back to browser only rendering.

Another consideration is a throwing function like just calling
`browser()` would throw if called during an SSR render. The main reason
we do not think this is a good idea is because you can then call this
arbitrarily deep and the throw can be caught and might be suppressed
accidentally. By making it a usable it can only be done in hooks or
hook-like contexts.
I've been triaging some lifecycle races during sessions with BFCache
involved and noticed a few type errors in real world scenario. These
objects were not properly typed, fixing in this PR.
The main reason why I am doing this is because there is no API in the
browser to "unmount" the created panel.

If you have React DevTools installed, we should always create a panel,
but the contents of the panel should be dynamically populated based on
the target. If it is not a React app, we will continue showing the stub
message.

Previously, we wouldn't mount a panel at all, and historically we've
received a few reports of this as a bug.
I have noticed inconsistency errors being thrown during browser
navigations that involve entries from BFCache. The main argument on why
this could be affecting React DevTools backend lifecycle is the fact
that Chrome kills the port manually, while freezing and preserving the
JavaScript heap -
https://developer.chrome.com/blog/bfcache-extension-messaging-changes.

Basically, we could end up in a permutation, where port is dead, but
Backend / Agent are alive. Such setup is not expected by React DevTools.

On `main`:


https://github.com/user-attachments/assets/9ca10286-b545-4384-bd6b-33d9a4ddde3d


With these changes:


https://github.com/user-attachments/assets/c4639c4a-6385-4248-a5c4-a39895d7fff6


I couldn't come up with a good test for this yet, but I will try to add
something. I am not convinced yet that emulating `pagehide` / `pageshow`
would be sufficient to reproduce browser environment during BFCache
entries.
@pull pull Bot locked and limited conversation to collaborators Jul 31, 2026
@pull pull Bot added the ⤵️ pull label Jul 31, 2026
@pull
pull Bot merged commit 0f42eac into code:main Jul 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants