0
0
RemixHow-ToIntermediate · 4 min read

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 with defer() 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 Await and React.Suspense in the component will break streaming.
  • Returning non-promise values inside defer() is fine, but promises must be handled with Await.
  • 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.