Capítulo 37 de 57

Chapter 37: Deferred Data Loading

Core Idea

By returning an unawaited promise from a route's loader, the router can render the next route as soon as the awaited (fast) data resolves, while slower, non-critical data streams in later and is consumed in the component via the Await component (or React 19's use() hook).

Key Concepts

  • Deferred promise: any promise value included, unawaited, in a loader's return object; awaited data still blocks the loader as normal, but deferred promises resolve after the route has begun rendering.
  • Await component: resolves a deferred promise by suspending the nearest suspense boundary until it settles, then renders children as a function receiving the resolved data; on rejection, it throws the serialized error to the nearest error boundary.
  • fallback prop: the loading UI shown by Await while its promise is pending.
  • React 19 use() hook: an alternative to Await for resolving promises directly in a React component.
  • External library deferral: when using TanStack Query or similar, deferred loading works differently, the loader calls queryClient.prefetchQuery (unawaited) for slow data and queryClient.ensureQueryData (awaited) for fast data; components then use useSuspenseQuery wrapped in a <Suspense> boundary for the slow data.
  • SSR streaming lifecycle: on the server, deferred promises are tracked and serialized into the initial HTML as they resolve; <Await> triggers suspense boundaries so the server can stream HTML incrementally; the client receives placeholder promises that resolve as streamed data arrives via inline script tags.
  • Caching parity: streamed/deferred promises follow the same lifecycle as their associated loader data, including support for preloading.

Code Examples

// src/routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: async () => {
    const slowDataPromise = fetchSlowData() // not awaited
    const fastData = await fetchFastData()
    return { fastData, deferredSlowData: slowDataPromise }
  },
  component: PostIdComponent,
})

function PostIdComponent() {
  const { deferredSlowData, fastData } = Route.useLoaderData()
  return (
    <Await promise={deferredSlowData} fallback={<div>Loading...</div>}>
      {(data) => <div>{data}</div>}
    </Await>
  )
}
  • What it demonstrates: splitting fast (awaited) and slow (deferred) data in one loader, then resolving the deferred part with Await.

Key Takeaways

  1. Deferring is the recommended strategy for slow, non-critical loader data; the alternative is showing a full-route pendingComponent (see Ch 36).
  2. Streaming SSR must be explicitly configured (see Ch 42) for deferred promises to stream on the server; without it, deferred data still works client-side only.
  3. When using TanStack Query instead of the built-in cache, skip Await entirely and use prefetchQuery/useSuspenseQuery with a manual <Suspense> boundary instead.

Connects To

  • Ch 36: Data Loading, the base loader mechanics and caching that deferred promises build on top of.
  • Ch 42: SSR's Streaming SSR section is required reading to make deferred data stream on the server.
  • Ch 38: External Data Loading covers the TanStack Query-based deferral pattern in more depth.