browser
browser lets you mark a component as browser-only during server rendering.
use(browser(reason?))Reference
browser(reason?)
Call browser inside use to mark a component as browser-only during server rendering:
import { use } from 'react';
import { browser } from 'react-dom';
function BrowserOnly() {
use(browser('This component requires browser APIs.'));
return <BrowserContent />;
}During server rendering, use(browser()) stops rendering the component and leaves the closest <Suspense> boundary’s fallback in its place. In the browser, use(browser()) returns undefined, so the component renders normally.
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 thecauseof theErrorpassed toonBrowserBailout. React calls a reason function each time a server renderer encounters the value returned bybrowser, but does not call it in the browser. If creating the reason is expensive, pass a function such as() => new Error(...).
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. In the browser, passing this value to use returns undefined.
Caveats
use(browser())must be inside a<Suspense>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, not a Server Component. - Calling
browser()by itself has no effect. To mark a component as browser-only, pass the value returned bybrowsertouse. Do not throw it.
Usage
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 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.
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 ( <label> Draft: <textarea value={draft} onChange={handleChange} rows={4} cols={30} /> </label> ); } export default function App() { return ( <> <h1>Saved draft</h1> <Suspense fallback={<p>Loading draft...</p>}> <SavedDraft /> </Suspense> </> ); }
Conditionally rendering in the browser
Like other calls to use, you can call use(browser()) conditionally or inside a custom Hook. For example, a custom Hook can return an initial value when it is provided, and read it from IndexedDB in the browser when it isn’t:
function useSetting(settingId, initialValue) {
if (initialValue !== undefined) {
return initialValue;
}
use(browser('No initial setting was provided.'));
return use(readSetting(settingId));
}On the server, useSetting returns initialValue when it is provided. Otherwise, the closest Suspense boundary’s fallback remains in the HTML. In the browser, use(browser()) returns undefined, so the Hook continues and reads the setting from IndexedDB.
In this example, the email notification setting is provided as initial data. The push notification setting is not, so click Reload to see its loading fallback while React reads it from IndexedDB.
import { Suspense } from 'react'; import { useSetting } from './useSetting.js'; function NotificationSetting({settingId, label, initialValue}) { const enabled = useSetting(settingId, initialValue); return ( <li> {label}: <strong>{enabled ? 'On' : 'Off'}</strong> </li> ); } export default function App() { return ( <> <h1>Notification settings</h1> <ul> <NotificationSetting settingId="email" label="Email notifications" initialValue={true} /> <Suspense fallback={<li>Loading push notification setting...</li>}> <NotificationSetting settingId="push" label="Push notifications" /> </Suspense> </ul> </> ); }
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 callback. This example also passes a reason, which is available as the reported error’s cause:
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 <DraftEditor initialDraft={draft} />;
}
const { pipe } = renderToPipeableStream(
<Suspense fallback={<p>Loading saved draft...</p>}>
<SavedDraft />
</Suspense>,
{
onShellReady() {
pipe(response);
},
onBrowserBailout(error, errorInfo) {
logBrowserBailout(error, errorInfo);
}
}
);onBrowserBailout receives two arguments:
- An
Errordescribing the browser-only render. If you passed a reason tobrowser, it is available as the error’scause. - An
errorInfoobject with acomponentStackshowing 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
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:
import { browser } from 'react-dom';
import { renderToPipeableStream } from 'react-dom/server';
const { pipe, abort } = renderToPipeableStream(<App />, {
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, pass browser() as the reason to AbortController.abort.