- Next.js
- Data Fetching & Request Lifecycle
- Request Waterfalls: Prevention with Promise.all and Suspense
Request Waterfalls: Prevention with Promise.all and Suspense
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
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;45 // Request 1: Takes 1000ms6 const user = await fetchUser(id);78 // Request 2: Blocked until Request 1 completes! Takes 1200ms9 const transactions = await fetchTransactions(id);1011 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
Does Suspense work for SEO crawlers?
Yes! Next.js streams HTML to the HTTP response. Search engine crawlers (like Googlebot) buffer streamed responses until the complete HTML stream finishes.
What happens if a promise inside Suspense throws an error?
The error bubbles up to the nearest error boundary (`error.tsx`), allowing other sections of the page to remain intact.
