0
0
RemixHow-ToBeginner · 4 min read

How to Use defer in Remix for Data Loading

In Remix, use the defer function inside your loader to return data that can load asynchronously and stream to the client. This lets parts of your page render immediately while waiting for slower data, improving performance and user experience.
📐

Syntax

The defer function wraps an object of promises returned from your loader. Remix streams the resolved data to your component as it becomes ready.

Example parts:

  • defer({ key: promise }): Wraps promises to defer their resolution.
  • useLoaderData(): Hook to access the deferred data in your component.
  • <Await>: Component to unwrap each deferred promise in the UI.
tsx
import { defer } from "@remix-run/node";
import { Await, useLoaderData } from "@remix-run/react";
import React from "react";

export function loader() {
  return defer({
    user: fetchUser(),
    posts: fetchPosts()
  });
}

export default function Page() {
  const data = useLoaderData();
  return (
    <>
      <React.Suspense fallback={<p>Loading user...</p>}>
        <Await resolve={data.user}>
          {(user) => <p>User: {user.name}</p>}
        </Await>
      </React.Suspense>
      <React.Suspense fallback={<p>Loading posts...</p>}>
        <Await resolve={data.posts}>
          {(posts) => <p>Posts count: {posts.length}</p>}
        </Await>
      </React.Suspense>
    </>
  );
}
💻

Example

This example shows a loader that defers fetching user and posts data. The page renders loading messages immediately and updates each section as data arrives.

tsx
import { defer } from "@remix-run/node";
import { Await, useLoaderData } from "@remix-run/react";
import React from "react";

function fetchUser() {
  return new Promise((resolve) =>
    setTimeout(() => resolve({ name: "Alice" }), 2000)
  );
}

function fetchPosts() {
  return new Promise((resolve) =>
    setTimeout(() => resolve([{ id: 1 }, { id: 2 }]), 4000)
  );
}

export function loader() {
  return defer({
    user: fetchUser(),
    posts: fetchPosts()
  });
}

export default function Page() {
  const data = useLoaderData();

  return (
    <>
      <React.Suspense fallback={<p>Loading user...</p>}>
        <Await resolve={data.user}>
          {(user) => <p>User: {user.name}</p>}
        </Await>
      </React.Suspense>

      <React.Suspense fallback={<p>Loading posts...</p>}>
        <Await resolve={data.posts}>
          {(posts) => <p>Posts count: {posts.length}</p>}
        </Await>
      </React.Suspense>
    </>
  );
}
Output
Loading user... User: Alice Loading posts... Posts count: 2
⚠️

Common Pitfalls

Not wrapping promises with defer: Returning plain data or unresolved promises without defer disables streaming and waits for all data.

Forgetting <Await>: You must use <Await> to unwrap deferred promises in your component, or you get unresolved promises in UI.

Not using React.Suspense: Wrap <Await> with React.Suspense to show fallback UI while waiting.

tsx
/* Wrong: returning promises without defer disables streaming */
function fetchUser() {
  return new Promise((resolve) =>
    setTimeout(() => resolve({ name: "Alice" }), 2000)
  );
}

function fetchPosts() {
  return new Promise((resolve) =>
    setTimeout(() => resolve([{ id: 1 }, { id: 2 }]), 4000)
  );
}

export function loader() {
  return {
    user: fetchUser(),
    posts: fetchPosts()
  };
}

/* Right: wrap promises with defer */
import { defer } from "@remix-run/node";
export function loader() {
  return defer({
    user: fetchUser(),
    posts: fetchPosts()
  });
}

/* Wrong: rendering promise directly */
import { useLoaderData } from "@remix-run/react";
export default function Page() {
  const data = useLoaderData();
  return <p>{String(data.user)}</p>; // Shows [object Promise]
}

/* Right: use <Await> and React.Suspense */
import { Await } from "@remix-run/react";
import React from "react";
export default function Page() {
  const data = useLoaderData();
  return (
    <React.Suspense fallback={<p>Loading user...</p>}>
      <Await resolve={data.user}>
        {(user) => <p>User: {user.name}</p>}
      </Await>
    </React.Suspense>
  );
}
📊

Quick Reference

  • defer(objectOfPromises): Wrap promises in loader to enable streaming.
  • useLoaderData(): Access deferred data in component.
  • <Await resolve={promise}>: Unwrap each deferred promise in UI.
  • React.Suspense fallback: Show loading UI while waiting for data.

Key Takeaways

Use defer in your loader to return promises for streaming data.
Wrap deferred promises in your component with <Await> inside React.Suspense for loading states.
Without defer, Remix waits for all data before rendering the page.
Always provide fallback UI in React.Suspense to improve user experience during loading.
Defer helps improve page speed by streaming data as it becomes ready.