React 19.3 creates browser() API to mark client-only components
React version 19.3, released on September 9, 2026, adds the browser() API to formally declare components that should not be rendered on the server.
What changed
React 19.3 was published on npm on September 9, 2026. Alongside already-stable features like View Transitions and Fragment Refs, the version brings a smaller piece that solves an old SSR annoyance: browser(), a new API from the react-dom package. It formalizes something every team already did the hard way, declaring that a component only makes sense once JavaScript has loaded in the browser.
The API is still in the Canary channel, not in the stable build most people install in production. Aurora Scharff documented the feature, and the reference is already published, but confirming the release channel before depending on it in production is part of the deal.
The problem it solves
Anyone who has done SSR knows the scene: a component needs to read localStorage, window, or the device's time zone, and none of that information exists on the server. The traditional workaround was one of two hacks. Either a useEffect with a mounted flag, or a typeof window !== 'undefined' check scattered through the component.
Both work, but both have the same side effect: the HTML the server sends differs from what React computes on the client's first render, and that triggers a hydration error. It's the kind of bug that shows up in the console in production, rarely in development, and that historically forced people to suppress the warning with extra workarounds instead of fixing the cause.
How browser() works in practice
The API is used with the use() hook, which already existed to handle promises and context. Calling use(browser()) inside a Client Component makes the component suspend during server rendering:
import { use } from 'react';
import { browser } from 'react-dom';
function LocalTime() {
use(browser());
return <p>{new Intl.DateTimeFormat().resolvedOptions().timeZone}</p>;
}On the server, that use(browser()) interrupts the component and leaves the HTML with the fallback from the nearest Suspense boundary. In the browser, the same call returns undefined, and the component renders normally after hydration. Two rules are mandatory: the component must be a Client Component and must be inside a Suspense boundary, otherwise server rendering fails.
Since use() can sit behind a condition, you can decide rendering on a case-by-case basis. A data hook, for example, can render on the server when a loader has already provided initialData and only skip to the client when it hasn't. React also adds an onBrowserBailout callback, with an optional reason argument, so the team can log exactly where and why server rendering was skipped.
What the community has already said about it
The change was born as a PR in React's core. The This Week in React newsletter described the practical effect this way:
Eliminating hacky server error-suppression workarounds.
This Week in React newsletter
In the documentation issue that accompanied the feature, a contributor summed up the gain:
Very excited about the browser() api because it solves the client-only component problem in a much cleaner way than the previous ways.
Contributor, React documentation issue
A developer, commenting on the release, went in the same direction when talking about the conditional variant with data:
The cleanest solution to the "should this fetch on the server or not" question I've seen.
Developer, in a post about the React 19.3 release
How this compares to what frameworks already had
Next.js users already had a similar emergency exit: dynamic(() => import('./C'), { ssr: false }), via next/dynamic. Astro has the client:only directive. Both work, but both are tied to each framework's bundler, which locks in anyone building an SSR stack outside those ecosystems, or anyone maintaining more than one framework across their project portfolio.
| Approach | Where it lives | When it renders on the server | Status |
|---|---|---|---|
browser() + use() | React (react-dom) | Never; suspends and uses the Suspense fallback | Canary |
next/dynamic with ssr: false | Next.js | Never; component only exists in the client bundle | Stable |
client:only | Astro | Never; island rendered only on the client | Stable |
| Standard SSR | Any React framework | Always, with full hydration | Stable |
The key difference is where the decision lives. With browser(), it lives in React, so it works in any SSR setup a team has already put together by hand, without depending on a specific bundler to sign off on the contract.
What this changes for app architecture in Brazil
Brazilian teams running custom SSR, whether with Vite plus an Express server or their own integration of React on top of another stack, gain an option that doesn't depend on reimplementing what Next.js or Remix already solve internally. This matters especially for those running React outside the most popular full-stack frameworks, a common case in corporate backends that chose to build their own rendering pipeline.
In short: before adopting it, it's worth measuring where in the project typeof window !== 'undefined' or a useEffect with a mount flag is already in use. Those spots are direct candidates for migration, but migrating in production only makes sense once browser() leaves Canary, since the API's shape can still change before it stabilizes.
What remains open
The official documentation still treats browser() as Canary only, with no public stabilization date. That means teams who decide to adopt it before the stable release need to watch React's changelogs for signature changes, and avoid shipping this dependency in shared internal libraries while the contract isn't locked in. The version bump itself is simple, a standard bump to react@19.3 and react-dom@19.3 following React 19's upgrade guide, but that updates the framework, it doesn't clear the still-experimental API for production use.
Translated from the Brazilian Portuguese original · Read the original
Liquid Clustering beats ZOrder in three of four workloads, benchmark shows in Delta Lake
Tests comparing Partitioning, ZOrder, and Liquid Clustering in Delta Lake reveal that no single data layout strategy wins on its own, and that each one has a blind spot capable of degrading production queries without anyone noticing.