How to Use Streaming in Remix for Faster Page Loads
In Remix, you can use
defer() to stream parts of your loader data progressively to the client. This lets the browser start rendering immediately while waiting for slower data, improving perceived load speed.Syntax
Use the defer() function inside your loader to return a streaming response. Wrap your data in defer() and use Await component in your React UI to handle the streamed data.
Parts:
loader(): Returns data wrapped withdefer()for streaming.defer(): Marks data to be streamed.Await: React component that waits for streamed data to resolve.
tsx
import { defer } from "@remix-run/node"; import { Await, useLoaderData } from "@remix-run/react"; import React from "react"; export async function loader() { const fastData = { message: "Fast data loaded" }; const slowDataPromise = new Promise(resolve => setTimeout(() => resolve({ message: "Slow data loaded" }), 3000) ); return defer({ fastData, slowData: slowDataPromise }); } export default function StreamingExample() { const data = useLoaderData(); return ( <div> <p>{data.fastData.message}</p> <React.Suspense fallback={<p>Loading slow data...</p>}> <Await resolve={data.slowData}> {(resolved) => <p>{resolved.message}</p>} </Await> </React.Suspense> </div> ); }
Output
<p>Fast data loaded</p>
<p>Loading slow data...</p> (for 3 seconds)
<p>Slow data loaded</p>
Example
This example shows how to stream fast data immediately and slow data after a delay using defer() and Await. The UI first renders the fast message, then shows a loading fallback while waiting for the slow message.
tsx
import { defer } from "@remix-run/node"; import { Await, useLoaderData } from "@remix-run/react"; import React from "react"; export async function loader() { const fastData = { message: "Fast data loaded" }; const slowDataPromise = new Promise(resolve => setTimeout(() => resolve({ message: "Slow data loaded" }), 3000) ); return defer({ fastData, slowData: slowDataPromise }); } export default function StreamingExample() { const data = useLoaderData(); return ( <div> <p>{data.fastData.message}</p> <React.Suspense fallback={<p>Loading slow data...</p>}> <Await resolve={data.slowData}> {(resolved) => <p>{resolved.message}</p>} </Await> </React.Suspense> </div> ); }
Output
<p>Fast data loaded</p>
<p>Loading slow data...</p> (for 3 seconds)
<p>Slow data loaded</p>
Common Pitfalls
- Not wrapping slow data in
defer()causes the whole page to wait for all data before rendering. - Forgetting to use
AwaitandReact.Suspensein the component will break streaming. - Returning non-promise values inside
defer()is fine, but promises must be handled withAwait. - Using streaming with non-React components or outside Remix loaders is not supported.
tsx
/* Wrong way: returning all data without defer */ export async function loader() { const fastData = { message: "Fast data loaded" }; const slowData = await new Promise(resolve => setTimeout(() => resolve({ message: "Slow data loaded" }), 3000) ); return { fastData, slowData }; } /* Right way: use defer and promises */ import { defer } from "@remix-run/node"; export async function loader() { const fastData = { message: "Fast data loaded" }; const slowDataPromise = new Promise(resolve => setTimeout(() => resolve({ message: "Slow data loaded" }), 3000) ); return defer({ fastData, slowData: slowDataPromise }); }
Quick Reference
- defer(data): Wraps data to stream promises.
- useLoaderData(): Access loader data in component.
- Await: React component to handle streamed promises.
- React.Suspense: Shows fallback UI while waiting.
Use streaming to improve user experience by showing fast data immediately and loading slow data progressively.
Key Takeaways
Use
defer() in Remix loaders to stream data progressively.Wrap slow-loading promises in
defer() and handle them with Await and React.Suspense in your component.Streaming improves perceived load speed by showing fast data immediately and loading slow data in the background.
Always return promises inside
defer() for streaming; otherwise, Remix waits for all data before rendering.Streaming only works with Remix loaders and React components using
Await and Suspense.