Performance: Deferred data with streaming
This affects how quickly the main content appears and how smoothly the page responds while loading additional data.
import { defer, Await, useLoaderData } from "@remix-run/react"; import { Suspense } from "react"; export async function loader() { const mainData = await fetchMainData(); const slowDataPromise = fetchSlowData(); return defer({ mainData, slowData: slowDataPromise }); } export default function Page() { const { mainData, slowData } = useLoaderData(); return <> <MainContent data={mainData} /> <Suspense fallback={<LoadingSpinner />}> <Await resolve={slowData}> {(data) => <SlowContent data={data} />} </Await> </Suspense> </>; }
export async function loader() { const mainData = await fetchMainData(); const slowData = await fetchSlowData(); return { mainData, slowData }; } export default function Page() { const { mainData, slowData } = useLoaderData(); return <> <MainContent data={mainData} /> <SlowContent data={slowData} /> </>; }
| Pattern | DOM Operations | Reflows | Paint Cost | Verdict |
|---|---|---|---|---|
| Blocking full data load | Single batch DOM update after all data arrives | 1 reflow after all data loads | High paint cost due to delayed content | [X] Bad |
| Deferred streaming with Suspense | Initial DOM update with main content, incremental update for slow data | 1 reflow for main content, 1 reflow for streamed data | Lower paint cost, faster first meaningful paint | [OK] Good |
defer() in Remix loaders?defer() does in Remix loadersdefer() allows parts of the data to load asynchronously, so the page can start rendering before all data arrives.defer().defer() in a Remix loader?defer() expects an object with keys mapping to promises or data. So defer({ user: fetchUser() }) is correct because it passes an object with a promise.deferUser. return defer({ user: fetchUser }) passes the function fetchUser without calling it, so it passes a function, not a promise.export async function loader() {
return defer({
user: fetchUser(),
posts: fetchPosts()
});
}<Suspense fallback="<p>Loading posts...</p>">
<Await resolve={data.posts}>
{(posts) => <PostList posts={posts} />}
</Await>
</Suspense>Suspense and Await only for posts, so posts will show a loading fallback until ready.export async function loader() {
const user = fetchUser();
return defer(user);
}defer() requires an object mapping keys to promises or data, not a single promise.defer({ user }) to fix the error.defer() with an object containing both user and posts lets you load them independently.Suspense and Await to show a loading message until posts arrive.Pass the test and this pattern moves unknown