Next.js Curriculum
MediumData Fetching100% Free30 min read

Request Waterfalls: Prevention with Promise.all and Suspense

The 30-Second Interview Answer

Request waterfalls occur when sequential async operations block subsequent requests unnecessarily. In Next.js, waterfalls are resolved using two primary patterns: parallel fetching with Promise.all() for colocated independent data, and granular Suspense boundaries to stream independent sub-trees concurrently without blocking the parent page.

Learning Objectives

Identify sequential async waterfalls in Server Component component trees.

Implement parallel data fetching with Promise.all() and Promise.allSettled().

Decompose monolith pages into parallel streaming widgets with React Suspense.

Optimize Time to First Byte (TTFB) while maintaining fast Largest Contentful Paint (LCP).

Prerequisites & Background Knowledge
JavaScript PromisesReact SuspenseTime to First Byte (TTFB) metrics

Why Interviewers Care

FAANG performance interviews routinely present candidate code with subtle waterfalls and evaluate refactoring strategies.

The Challenge

Diagnose the performance flaw in sequential async data fetching inside Server Components. Refactor sequential requests into parallel execution using Promise.all() and streaming Suspense boundaries.

Code Architecture & Implementation

1// SLOW: 1000ms + 1200ms = 2200ms total blocking server render!
2export default async function UserDashboard({ params }: { params: Promise<{ id: string }> }) {
3 const { id } = await params;
4
5 // Request 1: Takes 1000ms
6 const user = await fetchUser(id);
7
8 // Request 2: Blocked until Request 1 completes! Takes 1200ms
9 const transactions = await fetchTransactions(id);
10
11 return (
12 <div>
13 <UserProfile user={user} />
14 <TransactionList items={transactions} />
15 </div>
16 );
17}

Architectural Explanation

Sequential await statements in parent components block the entire server render until all promises resolve serially. Refactoring to Promise.all([fetchA(), fetchB()]) allows the Node.js runtime to issue network requests in parallel, cutting total latency to max(timeA, timeB). Even better, decomposing the page into independent Server Component widgets wrapped in <Suspense> allows Next.js to stream the instant shell to the client immediately and pipe HTML chunks as each individual promise resolves.

Common Mistakes & Anti-Patterns

Awaiting promises sequentially at the top of page.tsx when child components could fetch their own data inside Suspense boundaries.

Using Promise.all when one request is critical (user info) and the other is slow/non-critical (recommendations), blocking the entire response.

Failing to handle individual rejections in Promise.all, causing the entire page to crash if one non-critical endpoint fails.

Interview Follow-Up Traps

Interviewers will test: "If component A and component B both call getUser(id), does that cause two duplicate requests?" Explain that React `cache()` and Next.js fetch memoization deduplicate identical requests within a single render pass.

Frequently Asked Questions

When should I use Promise.all versus Suspense boundaries?

Use Promise.all when a single component strictly requires both datasets to compute its view. Use Suspense when different visual regions can load and display independently.

Yes! Next.js streams HTML to the HTTP response. Search engine crawlers (like Googlebot) buffer streamed responses until the complete HTML stream finishes.

The error bubbles up to the nearest error boundary (`error.tsx`), allowing other sections of the page to remain intact.