Skip to Content
React WidgetSuspense and Error Boundaries

Suspense and error boundaries

@evanion/react-widget 0.3.0 and its patch releases up to 0.3.3, the version on npm. Values on this page were produced by running the source at f9b1d93, a later commit than the 0.3.0 tag, and are not re-executed.

A slow widget should not hold up the rest of your page, and a failing widget should not take the entire region down with it. @evanion/react-widget provides hooks to integrate <Suspense> and Error Boundaries directly into the item chrome.

Choosing Where the <Suspense> Boundaries Go

By default, the Widgets component wraps every individual widget in its own <Suspense> boundary. This ensures that one slow widget (e.g., one fetching data from a legacy API) does not block its siblings from rendering.

This is controlled via chrome.suspense, which defaults to 'per-item'. You can also specify a chrome.suspenseFallback to render a custom loading state while a widget suspends.

const = async (: { : string }) => { const = await .(3); return ( <> {} of {.} in stock </> ); }; const { , } = ({ : { : }, : { : <>Counting stock</> }, }); const = ([ { : 'root-stock', : 'stock', : { : 'Root' } }, ]); const = (< ={} />); // -> '<section><div data-widget-id="root-stock" data-widget-type="stock"><p>Counting stock</p></div></section>'

In this example, StockLevel is an async Server Component. When using a streaming render, React sends the fallback (“Counting stock”) first and replaces it with the actual content once the promise resolves.

When to disable Suspense: chrome.suspense: 'none'

In a large streamed region, having hundreds of individual <Suspense> boundaries can lead to “outlined boundaries.” React may send the shell of the page first and move the content of every boundary into trailing <div hidden> tags, which are then moved into place by an inline script.

This can be problematic if:

  1. Scripts are disabled: Users with scripts turned off or a strict CSP will see placeholders instead of widgets.
  2. SEO/Indexing: Some text extractors or snapshot tests may struggle to find content moved to the end of the document.

If you know your widgets are all synchronous (they don’t use use(promise) or async components), set chrome.suspense: 'none'. This removes the boundaries entirely, ensuring all widgets are in the initial HTML shell.

const = (: { : string }) => <>{.}</>; const = ({ : { : } }); const = ({ : { : }, : { : 'none' }, }); const = .([ { : 'hive', : 'row', : { : 'Hive' } }, ]); const = (<. ={} />); // -> '<section><div data-widget-id="hive" data-widget-type="row"><!--$--><p>Hive</p><!--/$--></div></section>' const = (<. ={} />); // -> '<section><div data-widget-id="hive" data-widget-type="row"><p>Hive</p></div></section>'

Warning: If you set 'none' but a widget does suspend, it will suspend the nearest boundary above the <Widgets> region. To prevent a single widget from blocking the entire page, wrap the <Widgets> component in its own <Suspense> boundary.

Catching Widget Errors with Error Boundaries

@evanion/react-widget does not ship a built-in error boundary. This is because Error Boundaries must be class components, and including one in the package would force the entire library to be marked as 'use client', breaking its Server Component compatibility.

Instead, you should write your own error boundary in a separate file marked with 'use client'.

'use client'; class extends React.< { ?: React. }, { : boolean } > { = { : false }; static () { return { : true }; } () { return this.. ? ( <>This listing failed to load.</> ) : ( this.. ); } } const = (: React.<typeof >) => ( < {...}> <>{.}</> </> );

By creating a SafeItem component that wraps the widget in an error boundary and then passing it as chrome: { item: SafeItem }, you isolate failures. If a specific widget crashes, only that slot is replaced by the error fallback; the rest of the region continues to function.

Rearranging a Region in the Browser

The Playground allows you to interactively rearrange a Baize staff page. Since these widgets run entirely in the browser, you can see the effects of chrome and nesting in real-time without dealing with server-side streaming.

Last updated on