Suspense

<suspense> is the loading boundary for lazily loaded components: it shows a fallback while they are in flight, then swaps in the real thing.

<Suspense> is the same component.

source
<script>
const Chart = lazy('Chart')
const Map = lazy('Map')
</script>

<suspense fallback="<p>Loading...</p>">
  <Chart />
  <Map />
</suspense>

What it waits for is the tags this component claimed with lazy() - see Lazy Loading.

A boundary around ordinary components has nothing to wait for and mounts them straight away.

How it behaves

  • It waits for all the lazy children under it, then swaps once. The children mount through the ordinary pipeline: their scripts run, their bindings bind, their transitions play.
  • If every lazy child is already loaded, the children render immediately and the fallback never appears - not even for a frame.
  • The nearest boundary owns a tag. A nested <suspense> waits for its own children, and the outer one does not wait for them - two boundaries, two loading states.
source
<suspense fallback="<p>Loading the page...</p>">
  <Header />

  <!-- This one has its own boundary: the outer swap does not wait for it -->
  <suspense fallback="<p>Loading the chart...</p>">
    <Chart />
  </suspense>
</suspense>

fallback

fallback is the only prop.

There is no delay, timeout, minDuration or onLoad.

It is a literal HTML string, written straight into the boundary. The compiler never walks it, so two shapes are build errors naming the fallback rather than quiet disappointments: an expression, fallback="{count}", and a component tag, fallback="<Spinner />".

Write plain markup. Changed in 0.1 - the braces used to ship as three characters of text and the component tag used to render nothing at all.

It Is Not for Data

<suspense> has no connection to query(): it does not react to in-flight requests, and there is no "throw a promise" convention.

For loading states around data, read the query's own status:

source
<script>
const users = query("/api/users")
</script>

{users.loading && <p>Loading users...</p>}
{users.error && <p>Error: {users.error.message}</p>}
{users.data && users.data.map(u => <p>{u.name}</p>)}

See query() for the full status surface.