patterntypescriptnextjsModerate
Avoiding waterfall fetches: parallel data fetching with Promise.all in Server Components
Viewed 0 times
Next.js 13+ App Router
Promise.allparallel fetchwaterfalldata fetchingperformancesequential await
Problem
Using sequential await in a Server Component creates fetch waterfalls — each fetch waits for the previous one to finish. A page with three 500ms fetches takes 1500ms instead of 500ms.
Solution
Initiate all independent fetches in parallel using Promise.all:
// BAD: sequential — takes 1500ms total
export default async function Page() {
const user = await fetchUser(); // 500ms
const posts = await fetchPosts(); // 500ms after user
const comments = await fetchComments(); // 500ms after posts
return <Layout user={user} posts={posts} comments={comments} />;
}
// GOOD: parallel — takes 500ms total
export default async function Page() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments(),
]);
return <Layout user={user} posts={posts} comments={comments} />;
}
// When fetches depend on each other, use Suspense at component level:
export default function Page() {
return (
<>
<Suspense fallback={<UserSkeleton />}>
<UserSection />
</Suspense>
<Suspense fallback={<PostsSkeleton />}>
<PostsSection />
</Suspense>
</>
);
}
// BAD: sequential — takes 1500ms total
export default async function Page() {
const user = await fetchUser(); // 500ms
const posts = await fetchPosts(); // 500ms after user
const comments = await fetchComments(); // 500ms after posts
return <Layout user={user} posts={posts} comments={comments} />;
}
// GOOD: parallel — takes 500ms total
export default async function Page() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments(),
]);
return <Layout user={user} posts={posts} comments={comments} />;
}
// When fetches depend on each other, use Suspense at component level:
export default function Page() {
return (
<>
<Suspense fallback={<UserSkeleton />}>
<UserSection />
</Suspense>
<Suspense fallback={<PostsSkeleton />}>
<PostsSection />
</Suspense>
</>
);
}
Why
JavaScript await is sequential by nature — each await pauses execution. Promise.all starts all Promises simultaneously and waits for all to settle. For independent data fetches, parallel fetching is almost always faster and has no correctness downside.
Gotchas
- Promise.all fails fast — if any promise rejects, the entire call rejects. Use Promise.allSettled for independent error handling
- Requests to the same URL+options within a single request are deduplicated by Next.js memoization — safe to call the same fetch in multiple components
- Dependent fetches (user -> then user.teamId -> then team) must remain sequential
- Suspense boundaries at the component level achieve parallelism without blocking the parent
Code Snippets
Parallel data fetching with Promise.all
// Start all fetches in parallel
const [user, posts, stats] = await Promise.all([
fetchUser(id),
fetchUserPosts(id),
fetchUserStats(id),
]);
// Total time = longest fetch, not sum of allContext
When a Server Component needs to fetch multiple independent data sources
Revisions (0)
No revisions yet.