Lesson 69 of 81
Promise.all fixes fetches sitting side by side in one function. It can't fix a fetch waterfall caused by component nesting — where a child simply can't start rendering until its parent does.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A fetch waterfall happens when a parent Server Component awaits its own data before it renders its children — and one of those children also fetches data. The child's fetch physically cannot start until React begins rendering the child, and React can't render the child until the parent's await resolves and the parent function returns JSX. Two requests that had nothing to do with each other end up running back to back anyway, purely because of where they live in the tree.
This is a different problem from writing sequential awaits inside a single function (covered in the parallel-fetching lesson) — no Promise.all inside the parent fixes it, because the second fetch isn't even in the parent's code.
// app/dashboard/layout.tsx
async function getUser() {
const res = await fetch("https://api.example.com/me");
return res.json();
}
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const user = await getUser(); // blocks everything below
return (
<div>
<Sidebar userName={user.name} />
{children}
</div>
);
}
// app/dashboard/page.tsx
async function getStats(userId: string) {
const res = await fetch(`https://api.example.com/stats/${userId}`);
return res.json();
}
export default async function DashboardPage() {
// This can't start until getUser() in the layout has resolved,
// even though it doesn't need `user` — it just needs *a* user id
// it happens to get from a separate call.
const stats = await getStats("current");
return <StatsPanel stats={stats} />;
}
The getStats call has no data dependency on getUser — it's blocked purely by nesting order: React can't start rendering DashboardPage until DashboardLayout finishes awaiting and returns.
The real fix is to start the fetch as early as possible, independent of where it's awaited — often by kicking it off at the top of the tree (or in a shared request-scoped cache) and only consuming the result deeper down, so the two requests overlap on the wire even though they resolve in different components.
Promise.all can't fix a cross-component waterfall. It only helps when the multiple fetches live in the same function you control. Once one of them is inside a child component's own render, you need to restructure where the fetch happens, not just how it's awaited.use() hook) await it later — letting the fetch start early while not blocking the parent's own render on it.This is the same root cause as writing two independent awaits back to back in one function — needless serialization of work that could overlap — just expressed through component nesting instead of statement order. Wherever you see slow, staggered network requests in a Next.js app, check both: are the awaits in one function sequential when they don't need to be, and does the component tree itself force one fetch to wait on an unrelated one.