+
+ `);
+ doc.close();
+ }
+ return (
+ <>
+
+
+ >
+ );
+}
+```
+
+```js src/resources.js hidden
+// Add a unique parameter so the resources aren't cached,
+// and every run shows the loading state.
+export function freshStylesheetUrl() {
+ return (
+ 'https://fonts.googleapis.com/css2?family=Caveat&display=swap' +
+ '&t=' +
+ Date.now()
+ );
+}
+
+export function freshImageUrl() {
+ return 'https://react.dev/images/team/jack-pope.jpg?t=' + Date.now();
+}
+```
+
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+
+export async function fetchQuote() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise((resolve) => {
+ setTimeout(resolve, 250);
+ });
+ return 'The best way to predict the future is to invent it.';
+}
+```
+
+```css
+#root {
+ min-height: 320px;
+}
+button {
+ margin-right: 8px;
+}
+hr {
+ margin: 16px 0;
+}
+img {
+ object-fit: cover;
+}
+.profile-card {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ margin-top: 1em;
+}
+.profile-card img {
+ border-radius: 50%;
+ background: #dfe3e9;
+}
+.name {
+ margin: 0 0 4px;
+ font-family: 'Caveat', sans-serif;
+ font-size: 22px;
+ line-height: 28px;
+ font-weight: bold;
+}
+.bio {
+ margin: 0;
+ font-family: 'Caveat', sans-serif;
+ font-size: 20px;
+ line-height: 26px;
+}
+.profile-card img {
+ display: block;
+}
+.avatar-placeholder {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ background: #dfe3e9;
+}
+.name-placeholder,
+.bio-placeholder {
+ border-radius: 4px;
+ background: #dfe3e9;
+ color: transparent;
+}
+.name-placeholder {
+ width: 90px;
+}
+.bio-placeholder {
+ width: 220px;
+}
+.vanilla-frame {
+ display: block;
+ margin-top: 1em;
+ border: none;
+ width: 100%;
+ height: 110px;
+}
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-dom": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-scripts": "latest"
+ }
+}
+```
+
+
+
+To learn more about waiting for images, fonts, or stylesheets to load, see the [Suspense docs](/reference/react/Suspense#waiting-for-a-font-to-load).
+
+---
+
+### Fragment Refs {/*fragment-refs*/}
+
+When you need lower-level control over a component's DOM nodes—for example to attach an event listener, observe visibility, or move focus—you can usually use a ref. But there are some situations where this is difficult:
+
+- Components that render a group of siblings with no single parent
+- Components that don't pass along their `ref` prop to another element
+
+```js
+function Component() {
+ // How can we work with the list of DOM nodes rendered by this component?
+ return (
+ {posts.map(post => (
+
+ {post.title}
+
+ ))}
+ )
+}
+```
+
+Adding a wrapper `
` just to hold a ref sometimes works, but it can also interfere with your component's styling or layout. Moreover, if a component doesn't expose a `ref` prop, you would need to modify that component to do so, which might be impossible if it comes from a library you don't control.
+
+Fragment Refs solve these problems by providing a limited set of commonly used DOM methods that work with any React component, regardless of what it renders.
+
+In 19.3, you can use them by passing a ref directly to a [``](/reference/react/Fragment). This ref gives you a `FragmentInstance`, which you can use to work with the Fragment's DOM children:
+
+```js {2,5-6,10}
+function Component() {
+ const fragmentRef = useRef(null);
+
+ useEffect(() => {
+ const fragmentInstance = fragmentRef.current;
+ fragmentInstance.focus();
+ }, []);
+
+ return (
+
+ {posts.map(post => (
+
+ {post.title}
+
+ ))}
+
+ )
+}
+```
+
+The `FragmentInstance` operates on the children's DOM _as a group_, without changing its structure:
+
+- `addEventListener`, `removeEventListener`, and `dispatchEvent` manage events for first-level children.
+- `focus`, `focusLast`, and `blur` move focus across nested children, depth-first.
+- `observeUsing` and `unobserveUsing` connect an `IntersectionObserver` or `ResizeObserver`.
+- `getClientRects`, `getRootNode`, `compareDocumentPosition`, and `scrollIntoView` let you measure and scroll to the fragment's first-level children.
+
+Thus, Fragment Refs let you attach behavior to other components without requiring you to modify those component's internals, or without changing the DOM structure that they already produce.
+
+This example shows an `InView` component with an `onChange` prop that fires whenever its children enter or exit the viewport:
+
+
+
+```js src/App.js active
+import { useState } from 'react';
+import Card from './Card';
+import InView from './InView';
+
+export default function App() {
+ const [isVisible, setIsVisible] = useState(true);
+
+ return (
+
+
Scroll down
+
+
+
+
+
+
+
Scroll up
+
+ );
+}
+```
+
+```js src/Card.js
+export default function Card({ title }) {
+ return
{title}
;
+}
+```
+
+```js src/InView.js
+import {
+ Fragment,
+ useRef,
+ useLayoutEffect,
+} from 'react';
+
+export default function InView({ onChange, children }) {
+ const fragmentRef = useRef(null);
+
+ useLayoutEffect(() => {
+ const visibleElements = new Set();
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach(e => {
+ if (e.isIntersecting) {
+ visibleElements.add(e.target);
+ } else {
+ visibleElements.delete(e.target);
+ }
+ });
+ onChange(visibleElements.size > 0);
+ }
+ );
+ const fragmentInstance = fragmentRef.current;
+ fragmentInstance.observeUsing(observer);
+ return () => {
+ fragmentInstance.unobserveUsing(observer);
+ };
+ }, [onChange]);
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+```css
+.page {
+ transition: background 0.3s;
+}
+
+.page.visible {
+ background: #d4edda;
+}
+
+.filler {
+ height: 500px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #aaa;
+ font-size: 14px;
+}
+
+.card {
+ padding: 16px;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ margin: 8px 16px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08);
+ font-weight: 600;
+ font-size: 14px;
+}
+```
+
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-dom": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-scripts": "latest"
+ }
+}
+```
+
+
+
+Notice how `InView` is able to add behavior to its children, even though there's no single parent DOM element, and in spite of `Card` not exposing a `ref` prop.
+
+To learn more about working with Fragment Refs, see the [`` docs](/reference/react/Fragment).
+
+---
+
+## New React DOM Features {/*new-react-dom-features*/}
+
+### `browser` {/*browser*/}
+
+If your app uses server rendering, your components will render in two different environments:
+
+- On the server, components render to produce the initial HTML
+- On the client, components render to enrich that HTML with event handlers
+
+Most of time, your components should be able to produce HTML that matches their initial client-rendered output, ensuring they hydrate correctly while still letting users see as much content as possible on the initial load.
+
+But in rare cases, a component may not be able to produce meaningful UI on the server. For example, it might depend on a browser-only API like `localStorage`, or it might read from the browser's local timezone. In these cases, you may want to opt that component out of server rendering altogether.
+
+Previously, you might do this using some state that you'd update in an effect, or by checking for the presence of browser APIs like `window`:
+
+```js
+function Component() {
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ // ...
+}
+
+function Component() {
+ const isBrowser = typeof window !== 'undefined';
+
+ // ...
+}
+```
+
+In 19.3, React now includes a first-class API for this technique.
+
+A component can call `use(browser())` to opt out of server-side rendering:
+
+```js {5}
+import { use } from 'react';
+import { browser } from 'react-dom';
+
+function Component() {
+ use(browser());
+
+ // ...
+}
+```
+
+This will trigger Suspense on the server, but _not_ in the client. During server-side rendering, the nearest Suspense boundary's fallback will show in the HTML. Once the component is hydrated on the client, `use(browser())` does not suspend, allowing the component to continue rendering as normal.
+
+Here's an example of a component that renders the local time zone from your device. Press **Reload** to see the initial HTML followed by React's first render on the client:
+
+
+
+```js
+import { Suspense, use } from 'react';
+import { browser } from 'react-dom';
+
+function TimeZone() {
+ use(browser());
+ const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+ return
+
+
+
+ >
+ );
+}
+```
+
+
+```js src/Document.js hidden
+import App from './App.js';
+
+export default function Document() {
+ return (
+
+
+ Event details
+
+
+
+
+
+
+ );
+}
+```
+
+```js src/index.js hidden
+import { hydrateRoot } from 'react-dom/client';
+import { renderToReadableStream } from 'react-dom/server';
+import Document from './Document.js';
+import { flushReadableStreamToFrame } from './demo-helpers.js';
+import './styles.css';
+
+async function main(frame) {
+ const stream = await renderToReadableStream();
+ await flushReadableStreamToFrame(stream, frame);
+
+ // Wait so both the fallback and hydrated content are visible.
+ await new Promise(resolve => setTimeout(resolve, 1200));
+ hydrateRoot(frame.contentDocument, );
+}
+
+main(document.getElementById('preview'));
+```
+
+```js src/demo-helpers.js hidden
+export async function flushReadableStreamToFrame(readable, frame) {
+ const doc = frame.contentWindow.document;
+ const decoder = new TextDecoder();
+ const reader = readable.getReader();
+
+ while (true) {
+ const {done, value} = await reader.read();
+ if (done) {
+ break;
+ }
+ doc.write(decoder.decode(value, {stream: true}));
+ }
+
+ doc.write(decoder.decode());
+ doc.close();
+}
+```
+
+```html public/index.html hidden
+
+
+
+
+ Conditional browser rendering
+
+
+
+
+
+```
+
+```css src/styles.css hidden
+iframe {
+ width: 100%;
+ height: 240px;
+ border: 0;
+}
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-dom": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-scripts": "latest"
+ }
+}
+```
+
+
+
+Because TimeZone suspends on the server, the initial HTML includes the Suspense fallback. After a small artificial delay, React hydrates the page, allowing the component to render as normal in the browser.
+
+Thus, for components that cannot produce meaningful UI during server rendering, `browser` lets you use Suspense for their loading states, allowing them to participate with other components that suspend until they're ready to render.
+
+---
+
+Like other calls to `use`, `use(browser())` can be called inside a conditional statement or after an early return. This lets you write components or custom Hooks that can opt out of server rendering based on a condition, such as the value of a prop.
+
+Here's the same example from above, except now our TimeZone component accepts an optional default value it can render as part of the initial HTML:
+
+
+
+```js
+import { Suspense, use } from 'react';
+import { browser } from 'react-dom';
+
+function TimeZone({ defaultValue }) {
+ if (defaultValue) {
+ return
+ >
+ );
+}
+```
+
+
+```js src/Document.js hidden
+import App from './App.js';
+
+export default function Document() {
+ return (
+
+
+ Event details
+
+
+
+
+
+
+ );
+}
+```
+
+```js src/index.js hidden
+import { hydrateRoot } from 'react-dom/client';
+import { renderToReadableStream } from 'react-dom/server';
+import Document from './Document.js';
+import { flushReadableStreamToFrame } from './demo-helpers.js';
+import './styles.css';
+
+async function main(frame) {
+ const stream = await renderToReadableStream();
+ await flushReadableStreamToFrame(stream, frame);
+
+ // Wait so both the fallback and hydrated content are visible.
+ await new Promise(resolve => setTimeout(resolve, 1200));
+ hydrateRoot(frame.contentDocument, );
+}
+
+main(document.getElementById('preview'));
+```
+
+```js src/demo-helpers.js hidden
+export async function flushReadableStreamToFrame(readable, frame) {
+ const doc = frame.contentWindow.document;
+ const decoder = new TextDecoder();
+ const reader = readable.getReader();
+
+ while (true) {
+ const {done, value} = await reader.read();
+ if (done) {
+ break;
+ }
+ doc.write(decoder.decode(value, {stream: true}));
+ }
+
+ doc.write(decoder.decode());
+ doc.close();
+}
+```
+
+```html public/index.html hidden
+
+
+
+
+ Conditional browser rendering
+
+
+
+
+
+```
+
+```css src/styles.css hidden
+iframe {
+ width: 100%;
+ height: 240px;
+ border: 0;
+}
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-dom": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-scripts": "latest"
+ }
+}
+```
+
+
+
+Notice how TimeZone only suspends in the second case, when no default is provided.
+
+Another useful example of this pattern is opting a data-fetching Hook like `useQuery` out of server rendering, unless that query's initial data was passed in (for example from a Server Component or framework's loader function):
+
+```js {3}
+function useBrowserQuery(query, options) {
+ if (options.initialData === undefined) {
+ use(browser());
+ }
+
+ return useQuery(query, options);
+}
+
+function ProductDetails({ productId, initialData }) {
+ const product = useBrowserQuery(`/api/products/${productId}`, {
+ initialData,
+ });
+
+ return
{product.name}
;
+}
+```
+
+Now, the ProductDetails component can be included in the HTML, provided it receives `initialData` during server rendering. If not, it suspends until it gets rendered in the browser, at which point `useQuery` can fetch the data or read from its cache as normal.
+
+To learn more about `browser`, [check out the docs](/reference/react-dom/browser).
+
+---
+
+### Trusted Types support {/*trusted-types-support*/}
+
+React 19.3 integrates with the browser [Trusted Types API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API), a security feature that helps prevent DOM-based XSS attacks. When a site enforces Trusted Types with `Content-Security-Policy: require-trusted-types-for 'script'`, the browser requires that values passed to injection sinks like `innerHTML` are typed objects (`TrustedHTML`, `TrustedScript`, `TrustedScriptURL`) created through your sanitization policies, rather than raw strings.
+
+Previously, React always coerced values to strings (via `'' + value`) before passing them to DOM APIs, which turned Trusted Types objects back into plain strings the browser would reject. React now passes these values through without coercion, so the browser can validate them and your Trusted Types policies work as intended.
+
+---
+
+## New React Server Components Features {/*new-react-server-components-features*/}
+
+### `` can be rendered directly in Server Components {/*context-can-be-rendered-directly-in-server-components*/}
+
+While Server Components can't _create_ Context, they can _render_ Context by importing it from a `'use client'` module.
+
+Previously, this required the client module to export a separate wrapper component, often called a Provider:
+
+```js {7-9}
+// user-context.js
+'use client';
+import { createContext } from 'react';
+
+export const UserContext = createContext(null);
+
+export function UserProvider({ currentUser, children }) {
+ return {children};
+}
+```
+
+```js {8}
+// server-component.js
+import { UserProvider } from './user-context';
+
+export async function Layout({ children }) {
+ const currentUser = await getCurrentUser();
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+Notice that in this example, the provider does nothing other than pass the prop from the Server Component directly to the Context.
+
+In React 19.3, Server Components can import and render Context directly from a `'use client'` module, without an additional wrapping component:
+
+```js {5}
+// user-context.js
+'use client';
+import { createContext } from 'react';
+
+export const UserContext = createContext(null);
+```
+
+```js {8}
+// server-component.js
+import { UserContext } from './user-context';
+
+export async function Layout({ children }) {
+ const currentUser = await getCurrentUser();
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+This is especially useful for Contexts that solely exist to allow Server Components to share some data with the rest of the client tree.
+
+
+---
+
+## Changelog {/*changelog*/}
+
+Other notable changes
+- `react`: Render Transitions independently instead of entangling them into a single render, so a slow Transition no longer holds up unrelated ones [#37290](https://github.com/react/react/pull/37290)
+- `react-dom`: Double invoke Effects in Strict Mode during hydration, matching client-rendered roots [#35961](https://github.com/react/react/pull/35961)
+- `react`: Add a warning when `use` is used incorrectly in a conditional [#37104](https://github.com/react/react/pull/37104)
+- `react`: Rename "form state" to "action state" in `useActionState` error messages [#35790](https://github.com/react/react/pull/35790)
+- `react-dom`: Add support for `onFullscreenChange` and `onFullscreenError` events [#34621](https://github.com/react/react/pull/34621)
+- `react-dom`: Add support for the `maskType` SVG property [#35921](https://github.com/react/react/pull/35921)
+- `react-dom`: Support `fetchPriority` for module resources [#36835](https://github.com/react/react/pull/36835)
+- `react-dom`: Fire `onReset` when React automatically resets a form after a Server Action [#35176](https://github.com/react/react/pull/35176)
+- `react-dom`: Include the `submitter` in `submit` events [#35590](https://github.com/react/react/pull/35590)
+- `react-dom`: Recognize `credentialless` as a boolean attribute on iframes [#36148](https://github.com/react/react/pull/36148)
+- `react-dom`: Batch updates from `resize` events until the next frame [#35117](https://github.com/react/react/pull/35117)
+- `react-server`: Transport `Error.cause` [#35810](https://github.com/react/react/pull/35810) and `AggregateError.errors` [#36156](https://github.com/react/react/pull/36156) to the client
+- `react-server`: Add support for `` in Flight [#34697](https://github.com/react/react/pull/34697)
+
+Notable bug fixes
+
+- `react`: Fix `useDeferredValue` getting stuck on an old value [#36134](https://github.com/react/react/pull/36134)
+- `react`: Fix context propagation into Suspense fallbacks [#36160](https://github.com/react/react/pull/36160) and through suspended Suspense boundaries [#35839](https://github.com/react/react/pull/35839)
+- `react`: Fix a hang when updating a dehydrated Suspense boundary inside a hidden tree [#37135](https://github.com/react/react/pull/37135)
+- `react`: Fix `useSyncExternalStore` missing store mutations that happened while an `` tree was hidden [#36947](https://github.com/react/react/pull/36947)
+- `react`: Fix `useEffectEvent` to read the latest values in `forwardRef` and `memo` components [#34831](https://github.com/react/react/pull/34831)
+- `react`: Fix form status resetting when component state is updated [#34075](https://github.com/react/react/pull/34075)
+- `react`: Fix several Fast Refresh bugs with `lazy`, `memo`, and edits that change a component's kind [#36965](https://github.com/react/react/pull/36965), [#36964](https://github.com/react/react/pull/36964), [#36963](https://github.com/react/react/pull/36963), [#36950](https://github.com/react/react/pull/36950)
+- `react`: Fix a bug where `` was still hoisted to `` after the `` containing the `` changed mode from `visible` to `hidden` [#34983](https://github.com/react/react/pull/34983)
+- `react`: Don't let errors escape a hidden `` [#35074](https://github.com/react/react/pull/35074)
+- `react`: Hide portal contents rendered inside a hidden `` [#35091](https://github.com/react/react/pull/35091)
+- `react`: Don't reference the internal `` type in error messages [#35763](https://github.com/react/react/pull/35763)
+- `react-dom`: Fix focus for delegated and already-focused elements [#36010](https://github.com/react/react/pull/36010)
+- `react-dom`: Fix a `FragmentInstance` listener leak by normalizing capture options per the DOM spec [#36047](https://github.com/react/react/pull/36047)
+- `react-dom`: Fix a `` crash in Mobile Safari [#35337](https://github.com/react/react/pull/35337)
+- `react-dom`: Fix a `` crash with `SuspenseList` [#35520](https://github.com/react/react/pull/35520)
+- `react-dom`: Update `defaultValue` for `type="number"` inputs to match other input types [#36980](https://github.com/react/react/pull/36980)
+- `react-dom`: Avoid setting `innerHTML` when it hasn't changed [#36949](https://github.com/react/react/pull/36949)
+- `react-dom`: Fix a false-positive hydration mismatch on `nonce` attributes [#37030](https://github.com/react/react/pull/37030)
+- `react-dom`: Fix `react-dom/server` hanging on Deno [#35235](https://github.com/react/react/pull/35235)
+- `react-server`: Fix dropped `FormData` entries in `decodeReplyFromBusboy` [#36468](https://github.com/react/react/pull/36468)
+- `react-server`: Fix a stack overflow with deep async chains [#35612](https://github.com/react/react/pull/35612) and a `RangeError` from exponential debug info growth [#37481](https://github.com/react/react/pull/37481)
+
+For a full list of changes, please see the [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md).
+
+---
+
+_Thanks to [Sam Selikoff](https://x.com/samselikoff) for writing this post, and to [Matt Carroll](https://mattcarrollcode.com/), [Dan Abramov](https://bsky.app/profile/danabra.mov), and [Andrew Clark](https://x.com/acdlite) for reviewing this post._
diff --git a/src/content/blog/index.md b/src/content/blog/index.md
index d2930fea..b8b83a3d 100644
--- a/src/content/blog/index.md
+++ b/src/content/blog/index.md
@@ -12,6 +12,12 @@ You can also follow the [@react.dev](https://bsky.app/profile/react.dev) account
+
+
+React 19.3 adds new features like View Transitions, Fragment Refs, browser(), Trusted Types, and more. In this post ...
+
+
+
The React Foundation has officially launched under the Linux Foundation.
diff --git a/src/content/community/team.md b/src/content/community/team.md
index e321f240..4e307463 100644
--- a/src/content/community/team.md
+++ b/src/content/community/team.md
@@ -26,7 +26,7 @@ React work is organized into working groups, each responsible for an area of the
-
+
Ricky majored in theoretical math and somehow found himself on the React Native team for a couple years before joining the React team. When he's not programming you can find him snowboarding, biking, climbing, golfing, or closing GitHub issues that do not match the issue template.
@@ -38,10 +38,12 @@ React work is organized into working groups, each responsible for an area of the
## Working Group members {/*working-group-members*/}
-
+
+
+
@@ -77,16 +79,16 @@ React work is organized into working groups, each responsible for an area of the
- Mike went to grad school dreaming of becoming a professor but realized that he liked building things a lot more than writing grant applications. Mike joined Meta to work on Javascript infrastructure, which ultimately led him to work on the React Compiler. When not hacking on either Javascript or OCaml, Mike can often be found hiking or skiing in the Pacific Northwest.
+ Mike went to grad school dreaming of becoming a professor but realized that he liked building things a lot more than writing grant applications. Mike joined Meta to work on JavaScript infrastructure, which ultimately led him to work on the React Compiler. When not hacking on either Javascript or OCaml, Mike can often be found hiking or skiing in the Pacific Northwest.
-
+
-
+
-
+
Ruslan's introduction to UI programming started when he was a kid by manually editing HTML templates for his custom gaming forums. Somehow, he ended up majoring in Computer Science. He enjoys music, games, and memes. Mostly memes.
@@ -102,8 +104,6 @@ React work is organized into working groups, each responsible for an area of the
-
-
## Advisors {/*advisors*/}
@@ -115,7 +115,7 @@ React work is organized into working groups, each responsible for an area of the
- Like many others, Jimmy started programming with the hopes of being able to work in the gaming industry. Fast forward a few years, he somehow decided that React and Javascript were pretty fun and that helping other developers build fast experiences was a more interesting life goal. After starting his career at Meta, working on product infrastructure and (briefly) on React Native, Jimmy now works at Vercel, where he helps his team build Next.js. He sadly does not get much time for video games anymore.
+ Like many others, Jimmy started programming with the hopes of being able to work in the gaming industry. Fast forward a few years, he somehow decided that React and JavaScript were pretty fun and that helping other developers build fast experiences was a more interesting life goal. After starting his career at Meta, working on product infrastructure and (briefly) on React Native, Jimmy now works at Vercel, where he helps his team build Next.js. He sadly does not get much time for video games anymore.
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
new file mode 100644
index 00000000..00c60d37
--- /dev/null
+++ b/src/content/reference/react-dom/browser.md
@@ -0,0 +1,462 @@
+---
+title: browser
+---
+
+
+
+`browser` lets you mark a component as browser-only during server rendering.
+
+```js
+use(browser(reason?))
+```
+
+
+
+
+
+---
+
+## Reference {/*reference*/}
+
+### `browser(reason?)` {/*browser*/}
+
+Call `browser` inside [`use`](/reference/react/use) to mark a component as browser-only during server rendering:
+
+```js
+import { use } from 'react';
+import { browser } from 'react-dom';
+
+function BrowserOnly() {
+ use(browser('This component requires browser APIs.'));
+ return ;
+}
+```
+
+During server rendering, `use(browser())` stops rendering the component and leaves the closest [``](/reference/react/Suspense) boundary's fallback in its place. In the browser, `use(browser())` returns `undefined`, so the component renders normally.
+
+[See more examples below.](#usage)
+
+#### Parameters {/*parameters*/}
+
+* **optional** `reason`: A string or function that explains why the content needs to render in the browser. The string or the function's return value becomes the `cause` of the `Error` passed to [`onBrowserBailout`](#reporting-browser-only-rendering-on-the-server). React calls a reason function each time a server renderer encounters the value returned by `browser`, but does not call it in the browser. If creating the reason is expensive, pass a function such as `() => new Error(...)`.
+
+#### Returns {/*returns*/}
+
+`browser` returns an opaque value that you can pass to `use` in a component or use as the reason when [aborting a server render](#aborting-pending-server-rendering-for-the-browser). In the browser, passing this value to `use` returns `undefined`.
+
+#### Caveats {/*caveats*/}
+
+* `use(browser())` must be inside a `` boundary during server rendering. Without one, the server render fails.
+* In a React Server Components app, `use(browser())` must be called from a [Client Component](/reference/rsc/use-client), not a [Server Component](/reference/rsc/server-components).
+* Calling `browser()` by itself has no effect. To mark a component as browser-only, pass the value returned by `browser` to `use`. Do not throw it.
+
+---
+
+## Usage {/*usage*/}
+
+### Rendering content only in the browser {/*rendering-content-only-in-the-browser*/}
+
+Call `browser` inside `use` in a component that should only render in the browser:
+
+You can use this instead of checking `typeof window`, waiting for an [`Effect`](/reference/react/useEffect) to set mounted state, or using a framework option to disable server rendering.
+
+Click **Reload** to see the loading fallback in the initial HTML. After hydration, React displays the draft loaded from `localStorage`.
+
+
+
+```js src/App.js active
+import { Suspense, use, useState } from 'react';
+import { browser } from 'react-dom';
+
+function SavedDraft() {
+ use(browser('The draft is stored in localStorage.'));
+ const [draft, setDraft] = useState(
+ () => localStorage.getItem('draft') ?? ''
+ );
+
+ function handleChange(event) {
+ const nextDraft = event.target.value;
+ setDraft(nextDraft);
+ localStorage.setItem('draft', nextDraft);
+ }
+
+ return (
+
+ );
+}
+
+export default function App() {
+ return (
+ <>
+
Saved draft
+ Loading draft...}>
+
+
+ >
+ );
+}
+```
+
+```js src/Document.js hidden
+import App from './App.js';
+
+export default function Document() {
+ return (
+
+
+ Saved draft
+
+
+
+
+
+
+ );
+}
+```
+
+```js src/index.js hidden
+import { hydrateRoot } from 'react-dom/client';
+import { renderToReadableStream } from 'react-dom/server';
+import Document from './Document.js';
+import { flushReadableStreamToFrame } from './demo-helpers.js';
+import './styles.css';
+
+async function main(frame) {
+ const stream = await renderToReadableStream();
+ await flushReadableStreamToFrame(stream, frame);
+
+ // Wait so both the fallback and hydrated content are visible.
+ await new Promise(resolve => setTimeout(resolve, 1200));
+ hydrateRoot(frame.contentDocument, );
+}
+
+main(document.getElementById('preview'));
+```
+
+```js src/demo-helpers.js hidden
+export async function flushReadableStreamToFrame(readable, frame) {
+ const doc = frame.contentWindow.document;
+ const decoder = new TextDecoder();
+ const reader = readable.getReader();
+
+ while (true) {
+ const {done, value} = await reader.read();
+ if (done) {
+ break;
+ }
+ doc.write(decoder.decode(value, {stream: true}));
+ }
+
+ doc.write(decoder.decode());
+ doc.close();
+}
+```
+
+```html public/index.html hidden
+
+
+
+
+ Browser-only rendering
+
+
+
+
+
+```
+
+```css src/styles.css hidden
+iframe {
+ width: 100%;
+ height: 160px;
+ border: 0;
+}
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-dom": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-scripts": "latest"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ }
+}
+```
+
+
+
+
+
+In a React Server Components app, `use(browser())` must be called from a Client Component. If your framework uses Server Components by default, add the [`'use client'`](/reference/rsc/use-client) directive to that file or move the call to a child Client Component:
+
+```js {1}
+'use client';
+
+import { use, useState } from 'react';
+import { browser } from 'react-dom';
+
+export default function SavedDraft() {
+ use(browser('The saved draft is stored in localStorage.'));
+ const [draft] = useState(() => localStorage.getItem('draft') ?? '');
+ return ;
+}
+```
+
+
+
+---
+
+### Conditionally rendering on the server {/*conditionally-rendering-on-the-server*/}
+
+Like other calls to [`use`](/reference/react/use), `use(browser())` can be called inside a conditional statement or after an early return. This lets a Component or custom Hook opt out of server rendering based on a condition, such as the value of a prop.
+
+For example, this `useTimeZone` Hook accepts an optional default value. When provided, React renders the default value in the initial HTML and in the browser. Without a default value, the Component suspends during server rendering and shows the device's local time zone in the browser.
+
+Click **Reload** to see the loading fallback before the user's time zone appears.
+
+
+
+```js src/App.js
+import { Suspense } from 'react';
+import { useTimeZone } from './useTimeZone.js';
+
+function TimeZone({label, defaultTimeZone}) {
+ const timeZone = useTimeZone(defaultTimeZone);
+ return
+
+ Loading your time zone...}>
+
+
+ >
+ );
+}
+```
+
+```js src/useTimeZone.js active
+import { use } from 'react';
+import { browser } from 'react-dom';
+
+export function useTimeZone(defaultTimeZone) {
+ if (defaultTimeZone !== undefined) {
+ return defaultTimeZone;
+ }
+
+ use(browser('No default time zone was provided.'));
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
+}
+```
+
+```js src/Document.js hidden
+import App from './App.js';
+
+export default function Document() {
+ return (
+
+
+ Event details
+
+
+
+
+
+
+ );
+}
+```
+
+```js src/index.js hidden
+import { hydrateRoot } from 'react-dom/client';
+import { renderToReadableStream } from 'react-dom/server';
+import Document from './Document.js';
+import { flushReadableStreamToFrame } from './demo-helpers.js';
+import './styles.css';
+
+async function main(frame) {
+ const stream = await renderToReadableStream();
+ await flushReadableStreamToFrame(stream, frame);
+
+ // Wait so both the fallback and hydrated content are visible.
+ await new Promise(resolve => setTimeout(resolve, 1200));
+ hydrateRoot(frame.contentDocument, );
+}
+
+main(document.getElementById('preview'));
+```
+
+```js src/demo-helpers.js hidden
+export async function flushReadableStreamToFrame(readable, frame) {
+ const doc = frame.contentWindow.document;
+ const decoder = new TextDecoder();
+ const reader = readable.getReader();
+
+ while (true) {
+ const {done, value} = await reader.read();
+ if (done) {
+ break;
+ }
+ doc.write(decoder.decode(value, {stream: true}));
+ }
+
+ doc.write(decoder.decode());
+ doc.close();
+}
+```
+
+```html public/index.html hidden
+
+
+
+
+ Conditional browser rendering
+
+
+
+
+
+```
+
+```css src/styles.css hidden
+iframe {
+ width: 100%;
+ height: 240px;
+ border: 0;
+}
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-dom": "19.3.0-canary-f1f7ed2a-20260904",
+ "react-scripts": "latest"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ }
+}
+```
+
+
+
+You can apply a similar pattern to conditionally avoid server rendering when using a Suspense-enabled data-fetching library:
+
+```js {3}
+function useBrowserQuery(query, options) {
+ if (options.initialData === undefined) {
+ use(browser('useBrowserQuery: No initial data was provided.'));
+ }
+
+ return useQuery(query, options);
+}
+
+function ProductDetails({ productId, initialData }) {
+ const product = useBrowserQuery(`/api/products/${productId}`, {
+ initialData,
+ });
+
+ return
{product.name}
;
+}
+```
+
+With `initialData`, React renders the Component to HTML on the server. Without it, React leaves the closest [``](/reference/react/Suspense) boundary's fallback in the HTML. In the browser, `useQuery` can fetch the data or read it from its client cache as usual.
+
+---
+
+### Reporting browser-only rendering on the server {/*reporting-browser-only-rendering-on-the-server*/}
+
+Pass an `onBrowserBailout` callback to the server renderer to report browser-only rendering. When React leaves a Suspense fallback for the browser, it does not call the server renderer's `onError` callback or [`hydrateRoot`'s `onRecoverableError`](/reference/react-dom/client/hydrateRoot#error-logging-in-production) callback. This example also passes a reason, which is available as the reported error's `cause`:
+
+```js
+import { Suspense, use, useState } from 'react';
+import { browser } from 'react-dom';
+import { renderToPipeableStream } from 'react-dom/server';
+
+function SavedDraft() {
+ use(browser(() => new Error('The saved draft is stored in localStorage.')));
+ const [draft] = useState(() => localStorage.getItem('draft') ?? '');
+ return ;
+}
+
+function App() {
+ return (
+ Loading saved draft...}>
+
+
+ );
+}
+
+const { pipe } = renderToPipeableStream(, {
+ onShellReady() {
+ pipe(response);
+ },
+ onBrowserBailout(error, errorInfo) {
+ logBrowserBailout(error, errorInfo);
+ }
+});
+```
+
+`onBrowserBailout` receives two arguments:
+
+1. An `Error` describing the browser-only render. If you passed a reason to `browser`, it is available as the error's `cause`.
+2. An `errorInfo` object with a `componentStack` showing where browser-only rendering occurred.
+
+The reason function can return any value. Return a new `Error` to give the cause its own stack without creating the `Error` in the browser. React does not serialize the reason into the HTML.
+
+If there is no Suspense boundary to provide a fallback, the server render fails. React reports the failure through the renderer's usual error callbacks instead of `onBrowserBailout`.
+
+---
+
+### Aborting pending server rendering for the browser {/*aborting-pending-server-rendering-for-the-browser*/}
+
+If you call a server rendering API directly, you can stop waiting for pending content and let the browser finish rendering it. Pass the value returned by `browser` as the reason when aborting the server render. React then leaves pending Suspense boundaries in their fallback state and renders their content in the browser:
+
+```js {1,8}
+import { browser } from 'react-dom';
+import { renderToPipeableStream } from 'react-dom/server';
+
+const { pipe, abort } = renderToPipeableStream(, {
+ onShellReady() {
+ pipe(response);
+ setTimeout(() => {
+ abort(browser('The server render timed out.'));
+ }, 10000);
+ }
+});
+```
+
+A `browser` abort reason does not trigger the server renderer's `onError` callback or `hydrateRoot`'s `onRecoverableError` callback. Instead, the server renderer reports each recovered Suspense boundary to `onBrowserBailout`.
+
+For server rendering APIs that accept an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), pass `browser()` as the reason to [`AbortController.abort`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort).
diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md
index c48b6eb5..251818bc 100644
--- a/src/content/reference/react-dom/client/hydrateRoot.md
+++ b/src/content/reference/react-dom/client/hydrateRoot.md
@@ -45,6 +45,7 @@ React will attach to the HTML that exists inside the `domNode`, and take over ma
* **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown and an `errorInfo` object containing the `componentStack`.
* **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. Called with the `error` React throws, and an `errorInfo` object containing the `componentStack`. Some recoverable errors may include the original error cause as `error.cause`.
* **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as used on the server.
+ * **optional** `formState`: The form state from a form submission handled by a [Server Function](/reference/rsc/server-functions). If the page was rendered on the server in response to a submission of a form that uses [`useActionState`](/reference/react/useActionState) with a `permalink`, pass the resulting form state so that `useActionState` returns the submitted state instead of the `initialState`. Must be the same value as the `formState` passed to the [server renderer.](/reference/react-dom/server/renderToPipeableStream#parameters) This is typically passed through by your framework.
#### Returns {/*returns*/}
@@ -274,6 +275,8 @@ This only works one level deep, and is intended to be an escape hatch. Don’t o
---
+{/* TODO: Remove this subsection when browser is available in Stable. */}
+
### Handling different client and server content {/*handling-different-client-and-server-content*/}
If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a [state variable](/reference/react/useState) like `isClient`, which you can set to `true` in an [Effect](/reference/react/useEffect):
@@ -319,6 +322,10 @@ export default function App() {
This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration.
+Use this approach when you want the client-rendered content to be different from the initial server-rendered HTML.
+
+If a component should render only in the browser, call [`use(browser())`](/reference/react/use#use-browser) instead of waiting for an Effect.
+
This approach makes hydration slower because your components have to render twice. Be mindful of the user experience on slow connections. The JavaScript code may load significantly later than the initial HTML render, so rendering a different UI immediately after hydration may also feel jarring to the user.
diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md
index ff2f526a..f81788da 100644
--- a/src/content/reference/react-dom/components/common.md
+++ b/src/content/reference/react-dom/components/common.md
@@ -28,7 +28,7 @@ These special React props are supported for all built-in components:
* `children`: A React node (an element, a string, a number, [a portal,](/reference/react-dom/createPortal) an empty node like `null`, `undefined` and booleans, or an array of other React nodes). Specifies the content inside the component. When you use JSX, you will usually specify the `children` prop implicitly by nesting tags like `
`.
-* `dangerouslySetInnerHTML`: An object of the form `{ __html: '
some html
' }` with a raw HTML string inside. Overrides the [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property of the DOM node and displays the passed HTML inside. This should be used with extreme caution! If the HTML inside isn't trusted (for example, if it's based on user data), you risk introducing an [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerability. [Read more about using `dangerouslySetInnerHTML`.](#dangerously-setting-the-inner-html)
+* `dangerouslySetInnerHTML`: An object of the form `{ __html: '
some html
' }` with a raw HTML string or [`TrustedHTML`](https://developer.mozilla.org/en-US/docs/Web/API/TrustedHTML) value inside. Overrides the [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property of the DOM node and displays the passed HTML inside. This should be used with extreme caution! If the HTML inside isn't trusted (for example, if it's based on user data), you risk introducing an [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerability. [Read more about using `dangerouslySetInnerHTML`.](#dangerously-setting-the-inner-html)
* `ref`: A ref object from [`useRef`](/reference/react/useRef) or [`createRef`](/reference/react/createRef), or a [`ref` callback function,](#ref-callback) or a string for [legacy refs.](https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs) Your ref will be filled with the DOM element for this node. [Read more about manipulating the DOM with refs.](#manipulating-a-dom-node-with-a-ref)
@@ -924,7 +924,7 @@ For more advanced use cases, the `ref` attribute also accepts a [callback functi
### Dangerously setting the inner HTML {/*dangerously-setting-the-inner-html*/}
-You can pass a raw HTML string to an element like so:
+You can pass a raw HTML string or a [`TrustedHTML`](https://developer.mozilla.org/en-US/docs/Web/API/TrustedHTML) value to an element like so:
```js
const markup = { __html: '
some raw html
' };
@@ -933,6 +933,8 @@ return ;
**This is dangerous. As with the underlying DOM [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property, you must exercise extreme caution! Unless the markup is coming from a completely trusted source, it is trivial to introduce an [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerability this way.**
+If your site enforces [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API), pass a `TrustedHTML` value created by your security policy as `__html`. React passes the value to the browser without converting it to a string, allowing the browser to validate it. Your policy must still ensure that any input used to create the value is trusted and sanitized.
+
For example, if you use a Markdown library that converts Markdown to HTML, you trust that its parser doesn't contain bugs, and the user only sees their own input, you can display the resulting HTML like this:
diff --git a/src/content/reference/react-dom/index.md b/src/content/reference/react-dom/index.md
index d01bd656..daf82901 100644
--- a/src/content/reference/react-dom/index.md
+++ b/src/content/reference/react-dom/index.md
@@ -30,6 +30,12 @@ These APIs can be used to make apps faster by pre-loading resources such as scri
* [`preinit`](/reference/react-dom/preinit) lets you fetch and evaluate an external script or fetch and insert a stylesheet.
* [`preinitModule`](/reference/react-dom/preinitModule) lets you fetch and evaluate an ESM module.
+## Server Rendering APIs {/*server-rendering-apis*/}
+
+This API controls how components render on the server:
+
+* [`browser`](/reference/react-dom/browser) lets you mark a component as browser-only during server rendering.
+
---
## Entry points {/*entry-points*/}
diff --git a/src/content/reference/react-dom/server/index.md b/src/content/reference/react-dom/server/index.md
index 1856acd7..aa5d3709 100644
--- a/src/content/reference/react-dom/server/index.md
+++ b/src/content/reference/react-dom/server/index.md
@@ -15,7 +15,7 @@ The `react-dom/server` APIs let you server-side render React components to HTML.
These methods are only available in the environments with [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API), which includes browsers, Deno, and some modern edge runtimes:
* [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) renders a React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
-* [`resume`](/reference/react-dom/server/renderToPipeableStream) resumes [`prerender`](/reference/react-dom/static/prerender) to a [Readable Web Stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
+* [`resume`](/reference/react-dom/server/resume) resumes [`prerender`](/reference/react-dom/static/prerender) to a [Readable Web Stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
@@ -30,7 +30,7 @@ Node.js also includes these methods for compatibility, but they are not recommen
These methods are only available in the environments with [Node.js Streams:](https://nodejs.org/api/stream.html)
* [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) renders a React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html)
-* [`resumeToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) resumes [`prerenderToNodeStream`](/reference/react-dom/static/prerenderToNodeStream) to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html)
+* [`resumeToPipeableStream`](/reference/react-dom/server/resumeToPipeableStream) resumes [`prerenderToNodeStream`](/reference/react-dom/static/prerenderToNodeStream) to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html)
---
diff --git a/src/content/reference/react-dom/server/renderToPipeableStream.md b/src/content/reference/react-dom/server/renderToPipeableStream.md
index 9668e01b..c3fac16c 100644
--- a/src/content/reference/react-dom/server/renderToPipeableStream.md
+++ b/src/content/reference/react-dom/server/renderToPipeableStream.md
@@ -52,11 +52,16 @@ On the client, call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to
* **optional** `bootstrapScriptContent`: If specified, this string will be placed in an inline `