The defer() utility has been removed in React Router v7. Use streaming responses with the native Web Streams API or React Server Components instead.
Migration from v6
In React Router v6, defer() allowed you to stream parts of your loader data:
// v6
import { defer } from "@remix-run/react";
import { Await } from "@remix-run/react";
export async function loader() {
return defer({
critical: await getCriticalData(),
lazy: getLazyData(), // Promise
});
}
export default function Component() {
const data = useLoaderData();
return (
<div>
<h1>{data.critical}</h1>
<Suspense fallback={<Spinner />}>
<Await resolve={data.lazy}>
{(lazy) => <div>{lazy}</div>}
</Await>
</Suspense>
</div>
);
}
v7 Alternatives
React Router v7 recommends different approaches for streaming data:
1. Using React Server Components (RSC)
With RSC, you can stream components naturally:
import { Suspense } from "react";
export default async function Page() {
const critical = await getCriticalData();
return (
<div>
<h1>{critical}</h1>
<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>
</div>
);
}
async function LazyComponent() {
const lazy = await getLazyData();
return <div>{lazy}</div>;
}
2. Using Client-Side Data Fetching
Load critical data in the loader, fetch additional data on the client:
export async function loader() {
return { critical: await getCriticalData() };
}
export default function Component() {
const { critical } = useLoaderData();
const [lazy, setLazy] = useState(null);
useEffect(() => {
getLazyData().then(setLazy);
}, []);
return (
<div>
<h1>{critical}</h1>
{lazy ? <div>{lazy}</div> : <Spinner />}
</div>
);
}
3. Using Multiple Loaders
Split data loading across parent/child routes:
// routes/parent.tsx
export async function loader() {
return { critical: await getCriticalData() };
}
export default function Parent() {
const { critical } = useLoaderData();
return (
<div>
<h1>{critical}</h1>
<Suspense fallback={<Spinner />}>
<Outlet />
</Suspense>
</div>
);
}
// routes/parent.child.tsx
export async function loader() {
return { lazy: await getLazyData() };
}
export default function Child() {
const { lazy } = useLoaderData();
return <div>{lazy}</div>;
}
4. Using Web Streams API
For advanced streaming, use native Web Streams:
export async function loader() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Send critical data immediately
const critical = await getCriticalData();
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ critical })}\n\n`)
);
// Stream lazy data when ready
const lazy = await getLazyData();
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ lazy })}\n\n`)
);
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
}
Why was defer() removed?
React Router v7 focuses on modern patterns:
- React Server Components provide better streaming primitives
- Simpler mental model - less framework-specific APIs to learn
- Better performance - native streaming is more efficient
- Standard APIs - uses Web platform features instead of custom implementations
Migration Checklist
Identify defer() usage
Find all instances of defer() in your loaders
Analyze data requirements
Determine which data is critical vs. can be loaded later
Choose migration strategy
Pick the best alternative based on your use case:
- RSC for server-side streaming
- Client fetching for progressive enhancement
- Multiple loaders for route-based splitting
- Web Streams for advanced streaming
Update components
Remove <Await> components and replace with chosen strategy
Test thoroughly
Verify data loading behavior and user experience
See Also