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.