Streaming Data

Streaming Data and useAsyncValue

A loader that awaits everything makes the whole page wait for its slowest query. Streaming fixes that: await the data the page cannot render without and return the rest as an unawaited promise, so React Router 139,386 renders the shell immediately and fills it in later.

An article that renders before its comments doJavaScript
const loader = async ({ params }) => ({
  post: await getPost(params.id),          // fast: awaited, blocks the render
  comments: getComments(params.id),        // slow: deliberately not awaited
});
function Post() {
  const { post, comments } = useLoaderData();
  return <article>
    <h2>{post.title}</h2>
    <Suspense fallback={<p>Loading comments...</p>}>
      <Await resolve={comments} errorElement={<p>Comments failed to load.</p>}>
        {(list) => <ul>{list.map((c) => <li key={c.id}>{c.body}</li>)}</ul>}
      </Await></Suspense></article>;
}

<Await resolve={promise}> needs a <Suspense> boundary above it. Its children can be a render function receiving the resolved value, as above, or ordinary elements, in which case any component inside reads the value with useAsyncValue().

<Await errorElement> catches a rejection, and the element it renders reads the reason with useAsyncError(); with no errorElement, the rejection bubbles to the route's error boundary and useRouteError(). The defer() wrapper that React Router 6.4 required around streamed loader data was removed in version 7: a plain object with a promise-valued property is now enough, on client and server alike.

Streaming earns the extra components only when the slow part is genuinely slow and genuinely secondary — comments, recommendations, an analytics chart. If the page is meaningless without the data, await it.