browser
Mark a component as browser-only during server rendering.
This is React DOM’s browser() API with a compatibility implementation for React DOM versions that don’t ship it yet, where foxact provides a polyfill built on the same mechanism as noSSR, so it works with React 18 and React 19.2 as well.
During stream server-side rendering, use(browser()) stops rendering the component and leaves the closest <Suspense> boundary’s fallback in the generated HTML. In the browser, use(browser()) returns undefined and the component renders normally: the user sees the fallback first when the page loads, and the actual content once React hydrates the page. See Rendering content only in the browser at React docs.
use(browser()) must be inside a <Suspense> boundary during server rendering. Without one, the server render fails.
Like noSSR, browser() only works with React stream server-side rendering (renderToPipeableStream, renderToReadableStream, and frameworks built on them like Next.js). If you are using the synchronous renderToString, use useIsClient instead.
Usage
A code editor like CodeMirror is the textbook case: constructing an EditorView builds and measures its own DOM and sniffs navigator for platform-specific key bindings, so the component cannot be rendered on the server at all.
'use client';
import { use } from 'react';
import { browser } from 'foxact/browser';
import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';
function Editor({ value, onChange }) {
use(browser('CodeMirror needs a real DOM to construct its EditorView.'));
return <CodeMirror value={value} extensions={[javascript()]} onChange={onChange} />;
}<Suspense fallback={<pre>Loading editor...</pre>}>
<Editor value={draft} onChange={setDraft} />
</Suspense>The server HTML will include <pre>Loading editor...</pre>. It will be replaced by the <Editor /> component in the browser.
Providing a reason
browser() accepts an optional reason, either a string or a function returning anything (e.g. () => new Error('...')). The string, or the function’s return value, becomes the cause of the error reported by the server renderer, which is handy when you want to find out where and why your server HTML bails out to the browser.
The function is only called by the server renderer and never in the browser, so pass a function when creating the reason is expensive.
Differences from React DOM’s native browser()
When react-dom doesn’t export browser (React DOM 19.2 and below), foxact’s polyfill returns a value that use() unwraps to undefined in the browser, and that makes use() throw a recoverable error on the server. It is the same error noSSR() throws, carrying the Next.js BAILOUT_TO_CLIENT_SIDE_RENDERING digest and a recoverableError: 'NO_SSR' marker. The generated HTML is the same as with the native implementation, but the way the bailout is reported differs:
- The server renderer reports the bailout to
onErrorinstead ofonBrowserBailout. If you are callingrenderToPipeableStream/renderToReadableStreamyourself, filter it out, and return itsdigestso the browser can recognize it:
const { pipe } = renderToPipeableStream(<App />, {
onError(error) {
if (error && typeof error === 'object' && 'recoverableError' in error && error.recoverableError === 'NO_SSR') {
// A deliberate bailout requested by `browser()` (or `noSSR()`), not an error.
// Return the digest so the browser can tell it apart from real server errors.
return error.digest;
}
console.error(error);
}
});- In the browser, React reports the client-rendered boundary to
hydrateRoot’sonRecoverableError(the native implementation doesn’t). Filter it out by the digest you returned above:
import { hydrateRoot } from 'react-dom/client';
import { App } from './app';
hydrateRoot(document.getElementById('root'), <App />, {
onRecoverableError(error) {
if (error && typeof error === 'object' && 'digest' in error && error.digest === 'BAILOUT_TO_CLIENT_SIDE_RENDERING') {
// Ignoring the recoverable error generated by `browser()` (or `noSSR()`).
return;
}
// handle other errors
// ...
}
});Next.js already handles both of them.
- Passing
browser()as the reason ofabort()to abort a pending server render for the browser is only supported by the native implementation.
Once you upgrade to a React DOM that ships browser(), foxact hands you the native implementation, and all the differences above go away without any code change.