TanStack Router is designed to run loaders in parallel and wait for all of them to resolve before rendering the next route. This is great most of the time, but occasionally, you may want to show the user something sooner while the rest of the data loads in the background.
Deferred data loading is a pattern that allows the router to render the next location's critical data/markup while slower, non-critical route data is resolved in the background. This process works on both the client and server (via streaming) and is a great way to improve the perceived performance of your application.
To defer slow or non-critical data, wrap an unawaited/unresolved promise in the defer function and return it anywhere in your loader response:
// src/routes/posts.$postId.tsx
import * as React from 'react'
import { createFileRoute, defer } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async () => {
// Fetch some slower data, but do not await it
const slowDataPromise = fetchSlowData()
// Fetch and await some data that resolves quickly
const fastData = await fetchFastData()
return {
fastData,
// Wrap the slow promise in `defer()`
deferredSlowData: defer(slowDataPromise),
}
},
})
// src/routes/posts.$postId.tsx
import * as React from 'react'
import { createFileRoute, defer } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async () => {
// Fetch some slower data, but do not await it
const slowDataPromise = fetchSlowData()
// Fetch and await some data that resolves quickly
const fastData = await fetchFastData()
return {
fastData,
// Wrap the slow promise in `defer()`
deferredSlowData: defer(slowDataPromise),
}
},
})
As soon as any awaited promises are resolved, the next route will begin rendering while the deferred promises continue to resolve.
In the component, deferred promises can be resolved and utilized using the Await component:
// src/routes/posts.$postId.tsx
import * as React from 'react'
import { createFileRoute, Await } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const { deferredSlowData } = Route.useLoaderData()
return (
<Await promise={deferredSlowData} fallback={<div>Loading...</div>}>
{(data) => {
return <div>{data}</div>
}}
</Await>
)
}
// src/routes/posts.$postId.tsx
import * as React from 'react'
import { createFileRoute, Await } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const { deferredSlowData } = Route.useLoaderData()
return (
<Await promise={deferredSlowData} fallback={<div>Loading...</div>}>
{(data) => {
return <div>{data}</div>
}}
</Await>
)
}
Tip
If your component is code-split, you can use the getRouteApi function to avoid having to import the Route configuration to get access to the typed useLoaderData() hook.
The Await component resolves the promise by triggering the nearest suspense boundary until it is resolved, after which it renders the component's children as a function with the resolved data.
If the promise is rejected, the Await component will throw the serialized error, which can be caught by the nearest error boundary.
Streamed promises follow the same lifecycle as the loader data they are associated with. They can even be preloaded!
Streaming requires a server that supports it and for TanStack Router to be configured to use it properly.
Please read the entire SSR Guide for step by step instructions on how to set up your server for streaming.
The following is a high-level overview of how deferred data streaming works with TanStack Router:
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.