Skip to content

Commit 4a06359

Browse files
gnoffaurorascharff
authored andcommitted
Document browser-only rendering (reactjs#8582)
* Document browser-only rendering Add the Canary browser API reference, including optional lazy reasons, server bailout reporting, fatal and abort behavior, navigation entries, and onBrowserBailout options for every streaming, resume, and prerender API that supports it. * Polish browser API docs and add live example * Document browser with use and Suspense --------- Co-authored-by: Aurora Scharff <aurora.sofie@gmail.com>
1 parent 9c550d2 commit 4a06359

13 files changed

Lines changed: 591 additions & 0 deletions
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
---
2+
title: browser
3+
version: canary
4+
---
5+
6+
<Intro>
7+
8+
<Canary>
9+
10+
**The `browser` API is currently only available in React’s Canary and Experimental channels.**
11+
12+
[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
13+
14+
</Canary>
15+
16+
`browser` lets you mark a component as browser-only during server rendering.
17+
18+
```js
19+
use(browser(reason?))
20+
```
21+
22+
</Intro>
23+
24+
<InlineToc />
25+
26+
---
27+
28+
## Reference {/*reference*/}
29+
30+
### `browser(reason?)` {/*browser*/}
31+
32+
Call `browser` inside [`use`](/reference/react/use) to mark a component as browser-only during server rendering:
33+
34+
```js
35+
import { use } from 'react';
36+
import { browser } from 'react-dom';
37+
38+
function BrowserOnly() {
39+
use(browser('This component requires browser APIs.'));
40+
return <BrowserContent />;
41+
}
42+
```
43+
44+
During server rendering, `use(browser())` stops rendering the component and leaves the closest [`<Suspense>`](/reference/react/Suspense) boundary's fallback in its place. In the browser, `use(browser())` returns `undefined`, so the component renders normally.
45+
46+
[See more examples below.](#usage)
47+
48+
#### Parameters {/*parameters*/}
49+
50+
* **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(...)`.
51+
52+
#### Returns {/*returns*/}
53+
54+
`browser` returns a 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`.
55+
56+
#### Caveats {/*caveats*/}
57+
58+
* `use(browser())` must be inside a `<Suspense>` boundary during server rendering. Without one, the server render fails.
59+
* 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).
60+
* 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.
61+
62+
---
63+
64+
## Usage {/*usage*/}
65+
66+
### Rendering content only in the browser {/*rendering-content-only-in-the-browser*/}
67+
68+
Call `browser` inside `use` in a component that should only render in the browser:
69+
70+
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.
71+
72+
Press **Render the page**. The loading fallback appears first. After a short delay, React hydrates the page and displays the browser-only editor.
73+
74+
<Sandpack>
75+
76+
```js src/App.js active
77+
import { Suspense, use } from 'react';
78+
import { browser } from 'react-dom';
79+
80+
function BrowserOnlyEditor() {
81+
use(browser('The editor requires browser APIs.'));
82+
return <label>Draft: <input /></label>;
83+
}
84+
85+
export default function App() {
86+
return (
87+
<Suspense fallback={<p>Loading editor...</p>}>
88+
<BrowserOnlyEditor />
89+
</Suspense>
90+
);
91+
}
92+
```
93+
94+
```js src/Document.js hidden
95+
import App from './App.js';
96+
97+
export default function Document() {
98+
return (
99+
<html lang="en">
100+
<head>
101+
<title>Article editor</title>
102+
</head>
103+
<body>
104+
<h1>Article editor</h1>
105+
<App />
106+
</body>
107+
</html>
108+
);
109+
}
110+
```
111+
112+
```js src/index.js
113+
import { hydrateRoot } from 'react-dom/client';
114+
import { renderToReadableStream } from 'react-dom/server';
115+
import Document from './Document.js';
116+
import { flushReadableStreamToFrame } from './demo-helpers.js';
117+
import './styles.css';
118+
119+
async function main(frame) {
120+
const stream = await renderToReadableStream(<Document />);
121+
await flushReadableStreamToFrame(stream, frame);
122+
123+
// Wait so both the fallback and hydrated content are visible.
124+
await new Promise(resolve => setTimeout(resolve, 1200));
125+
hydrateRoot(frame.contentDocument, <Document />);
126+
}
127+
128+
const renderButton = document.getElementById('render');
129+
renderButton.addEventListener('click', () => {
130+
renderButton.disabled = true;
131+
main(document.getElementById('preview'));
132+
}, { once: true });
133+
```
134+
135+
```js src/demo-helpers.js hidden
136+
export async function flushReadableStreamToFrame(readable, frame) {
137+
const doc = frame.contentWindow.document;
138+
const decoder = new TextDecoder();
139+
for await (const chunk of readable) {
140+
doc.write(decoder.decode(chunk, { stream: true }));
141+
}
142+
doc.close();
143+
}
144+
```
145+
146+
```html public/index.html
147+
<!DOCTYPE html>
148+
<html lang="en">
149+
<head>
150+
<meta charset="UTF-8" />
151+
<title>Browser-only rendering</title>
152+
</head>
153+
<body>
154+
<button id="render">Render the page</button>
155+
<br /><br />
156+
<iframe id="preview" title="Rendered page"></iframe>
157+
</body>
158+
</html>
159+
```
160+
161+
```css src/styles.css hidden
162+
iframe {
163+
width: 100%;
164+
height: 180px;
165+
border: 1px solid #aaa;
166+
}
167+
```
168+
169+
```json package.json hidden
170+
{
171+
"dependencies": {
172+
"react": "canary",
173+
"react-dom": "canary",
174+
"react-scripts": "latest"
175+
},
176+
"scripts": {
177+
"start": "react-scripts start",
178+
"build": "react-scripts build",
179+
"test": "react-scripts test --env=jsdom",
180+
"eject": "react-scripts eject"
181+
}
182+
}
183+
```
184+
185+
</Sandpack>
186+
187+
<Note>
188+
189+
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:
190+
191+
```js {1}
192+
'use client';
193+
194+
import { use } from 'react';
195+
import { browser } from 'react-dom';
196+
197+
export default function BrowserOnlyEditor() {
198+
use(browser('The editor requires browser APIs.'));
199+
return <Editor />;
200+
}
201+
```
202+
203+
</Note>
204+
205+
---
206+
207+
### Conditionally rendering in the browser {/*conditionally-rendering-in-the-browser*/}
208+
209+
Like other calls to [`use`](/reference/react/use), you can call `use(browser())` conditionally or inside a custom Hook. For example, you can wrap a Suspense-enabled data-fetching library's `useQuery` and skip server rendering when initial data is missing:
210+
211+
```js {3}
212+
function useBrowserQuery(query, options) {
213+
if (options.initialData === undefined) {
214+
use(browser('useBrowserQuery: No initial data was provided.'));
215+
}
216+
217+
return useQuery(query, options);
218+
}
219+
220+
function ProductDetails({ productId, initialData }) {
221+
const product = useBrowserQuery(`/api/products/${productId}`, {
222+
initialData,
223+
});
224+
225+
return <h1>{product.name}</h1>;
226+
}
227+
```
228+
229+
On the server, `useBrowserQuery` calls `useQuery` only when `initialData` is available. Otherwise, the closest Suspense boundary's fallback remains in the HTML. In the browser, `use(browser())` returns `undefined`, so the query library can fetch the data or read it from its client cache.
230+
231+
---
232+
233+
### Reporting browser-only rendering on the server {/*reporting-browser-only-rendering-on-the-server*/}
234+
235+
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`:
236+
237+
```js
238+
import { Suspense, use } from 'react';
239+
import { browser } from 'react-dom';
240+
import { renderToPipeableStream } from 'react-dom/server';
241+
242+
function BrowserOnlyEditor() {
243+
use(browser(() => new Error('The editor requires a browser API.')));
244+
return <Editor />;
245+
}
246+
247+
const { pipe } = renderToPipeableStream(
248+
<Suspense fallback={<p>Loading editor...</p>}>
249+
<BrowserOnlyEditor />
250+
</Suspense>,
251+
{
252+
onShellReady() {
253+
pipe(response);
254+
},
255+
onBrowserBailout(error, errorInfo) {
256+
logBrowserBailout(error, errorInfo);
257+
}
258+
}
259+
);
260+
```
261+
262+
`onBrowserBailout` receives two arguments:
263+
264+
1. An `Error` describing the browser-only render. If you passed a reason to `browser`, it is available as the error's `cause`.
265+
2. An `errorInfo` object with a `componentStack` showing where browser-only rendering occurred.
266+
267+
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.
268+
269+
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`.
270+
271+
---
272+
273+
### Aborting pending server rendering for the browser {/*aborting-pending-server-rendering-for-the-browser*/}
274+
275+
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:
276+
277+
```js {1,8}
278+
import { browser } from 'react-dom';
279+
import { renderToPipeableStream } from 'react-dom/server';
280+
281+
const { pipe, abort } = renderToPipeableStream(<App />, {
282+
onShellReady() {
283+
pipe(response);
284+
setTimeout(() => {
285+
abort(browser('The server render timed out.'));
286+
}, 10000);
287+
}
288+
});
289+
```
290+
291+
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`.
292+
293+
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).

src/content/reference/react-dom/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ These APIs can be used to make apps faster by pre-loading resources such as scri
3030
* [`preinit`](/reference/react-dom/preinit) lets you fetch and evaluate an external script or fetch and insert a stylesheet.
3131
* [`preinitModule`](/reference/react-dom/preinitModule) lets you fetch and evaluate an ESM module.
3232

33+
## Server Rendering APIs {/*server-rendering-apis*/}
34+
35+
This API controls how components render on the server:
36+
37+
* <CanaryBadge /> [`browser`](/reference/react-dom/browser) lets you mark a component as browser-only during server rendering.
38+
3339
---
3440

3541
## Entry points {/*entry-points*/}

src/content/reference/react-dom/server/renderToPipeableStream.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ On the client, call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to
5656
* **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML.
5757
* **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src).
5858
* **optional** `onAllReady`: A callback that fires when all rendering is complete, including both the [shell](#specifying-what-goes-into-the-shell) and all additional [content.](#streaming-more-content-as-it-loads) You can use this instead of `onShellReady` [for crawlers and static generation.](#waiting-for-all-content-to-load-for-crawlers-and-static-generation) If you start streaming here, you won't get any progressive loading. The stream will contain the final HTML.
59+
* <CanaryBadge /> **optional** `onBrowserBailout`: A callback React calls when it recovers from [`browser()`](/reference/react-dom/browser) by leaving a Suspense fallback for the browser to replace. It receives an `Error` describing the browser-only render and an `errorInfo` object containing the `componentStack`. If a reason was passed to `browser`, it is available as `error.cause`. By default, React does nothing. [See how to report browser-only rendering.](/reference/react-dom/browser#reporting-browser-only-rendering-on-the-server)
5960
* **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](#recovering-from-errors-outside-the-shell) or [not.](#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](#setting-the-status-code) before the shell is emitted.
6061
* **optional** `onShellReady`: A callback that fires right after the [initial shell](#specifying-what-goes-into-the-shell) has been rendered. You can [set the status code](#setting-the-status-code) and call `pipe` here to start streaming. React will [stream the additional content](#streaming-more-content-as-it-loads) after the shell along with the inline `<script>` tags that replace the HTML loading fallbacks with the content.
6162
* **optional** `onShellError`: A callback that fires if there was an error rendering the initial shell. It receives the error as an argument. No bytes were emitted from the stream yet, and neither `onShellReady` nor `onAllReady` will get called, so you can [output a fallback HTML shell.](#recovering-from-errors-inside-the-shell)

src/content/reference/react-dom/server/renderToReadableStream.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ On the client, call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to
5656
* **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 passed to [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot#parameters)
5757
* **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML.
5858
* **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src).
59+
* <CanaryBadge /> **optional** `onBrowserBailout`: A callback React calls when it recovers from [`browser()`](/reference/react-dom/browser) by leaving a Suspense fallback for the browser to replace. It receives an `Error` describing the browser-only render and an `errorInfo` object containing the `componentStack`. If a reason was passed to `browser`, it is available as `error.cause`. By default, React does nothing. [See how to report browser-only rendering.](/reference/react-dom/browser#reporting-browser-only-rendering-on-the-server)
5960
* **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](#recovering-from-errors-outside-the-shell) or [not.](#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](#setting-the-status-code) before the shell is emitted.
6061
* **optional** `progressiveChunkSize`: The number of bytes in a chunk. [Read more about the default heuristic.](https://github.com/react/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225)
6162
* **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client.

src/content/reference/react-dom/server/resume.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ async function handler(request, writable) {
4848
* **optional** `options`: An object with streaming options.
4949
* **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src).
5050
* **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client.
51+
* <CanaryBadge /> **optional** `onBrowserBailout`: A callback React calls when it recovers from [`browser()`](/reference/react-dom/browser) by leaving a Suspense fallback for the browser to replace. It receives an `Error` describing the browser-only render and an `errorInfo` object containing the `componentStack`. If a reason was passed to `browser`, it is available as `error.cause`. By default, React does nothing. [See how to report browser-only rendering.](/reference/react-dom/browser#reporting-browser-only-rendering-on-the-server)
5152
* **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`.
5253
5354

src/content/reference/react-dom/server/resumeToPipeableStream.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ async function handler(request, response) {
5151
* **optional** `options`: An object with streaming options.
5252
* **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src).
5353
* **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client.
54+
* <CanaryBadge /> **optional** `onBrowserBailout`: A callback React calls when it recovers from [`browser()`](/reference/react-dom/browser) by leaving a Suspense fallback for the browser to replace. It receives an `Error` describing the browser-only render and an `errorInfo` object containing the `componentStack`. If a reason was passed to `browser`, it is available as `error.cause`. By default, React does nothing. [See how to report browser-only rendering.](/reference/react-dom/browser#reporting-browser-only-rendering-on-the-server)
5455
* **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`.
5556
* **optional** `onShellReady`: A callback that fires right after the [shell](#specifying-what-goes-into-the-shell) has finished. You can call `pipe` here to start streaming. React will [stream the additional content](#streaming-more-content-as-it-loads) after the shell along with the inline `<script>` tags that replace the HTML loading fallbacks with the content.
5657
* **optional** `onShellError`: A callback that fires if there was an error rendering the shell. It receives the error as an argument. No bytes were emitted from the stream yet, and neither `onShellReady` nor `onAllReady` will get called, so you can [output a fallback HTML shell](#recovering-from-errors-inside-the-shell) or use the prelude.

0 commit comments

Comments
 (0)