Skip to main content
Practice Problems

Parallel and sequential data fetching in Next.js

Data Fetching Patterns in Next.js

How you structure your data fetching significantly impacts page load performance. Next.js Server Components support both parallel and sequential patterns.


Sequential Data Fetching (Waterfall)

Each request waits for the previous one to complete:

tsx
// ❌ Sequential — slow! Each fetch waits for the previous one async function UserProfile({ userId }: { userId: string }) { const user = await getUser(userId); // 200ms const posts = await getUserPosts(user.id); // 300ms (waits for user) const followers = await getFollowers(user.id); // 200ms (waits for posts) // Total: ~700ms (200 + 300 + 200) return ( <div> <h1>{user.name}</h1> <PostList posts={posts} /> <FollowerList followers={followers} /> </div> ); }

Parallel Data Fetching

Fetch all data simultaneously with Promise.all:

tsx
// ✅ Parallel — fast! All requests start at the same time async function UserProfile({ userId }: { userId: string }) { const [user, posts, followers] = await Promise.all([ getUser(userId), // 200ms getUserPosts(userId), // 300ms (starts simultaneously) getFollowers(userId), // 200ms (starts simultaneously) ]); // Total: ~300ms (max of all three) return ( <div> <h1>{user.name}</h1> <PostList posts={posts} /> <FollowerList followers={followers} /> </div> ); }

Component-Level Parallel Fetching

Let each component fetch its own data — Next.js handles them in parallel:

tsx
// Parent component — no data fetching here export default function Dashboard() { return ( <div> <UserStats /> {/* fetches independently */} <RecentOrders /> {/* fetches independently */} <Notifications /> {/* fetches independently */} </div> ); } // Each child fetches its own data async function UserStats() { const stats = await getStats(); // Runs in parallel with others return <StatsCard stats={stats} />; } async function RecentOrders() { const orders = await getOrders(); // Runs in parallel return <OrderList orders={orders} />; } async function Notifications() { const notifs = await getNotifications(); // Runs in parallel return <NotifList notifications={notifs} />; }

Streaming with Suspense

Show content progressively as data arrives:

tsx
import { Suspense } from "react"; export default function Dashboard() { return ( <div> {/* Shows immediately */} <h1>Dashboard</h1> {/* Shows skeleton, then real content when ready */} <Suspense fallback={<StatsSkeleton />}> <UserStats /> </Suspense> <Suspense fallback={<OrdersSkeleton />}> <RecentOrders /> </Suspense> <Suspense fallback={<NotifsSkeleton />}> <Notifications /> </Suspense> </div> ); }

Benefits of streaming:

  • Users see content immediately (no blank page)
  • Fast content appears first, slow content streams in
  • Each section has its own loading state

When Dependencies Exist

Sometimes you need sequential fetching:

tsx
async function UserProfile({ userId }: { userId: string }) { // Step 1: Must get user first (need user.teamId) const user = await getUser(userId); // Step 2: Now fetch in parallel using user data const [team, posts] = await Promise.all([ getTeam(user.teamId), // Needs user.teamId getUserPosts(user.id), // Needs user.id ]); return ( <div> <h1>{user.name}</h1> <TeamInfo team={team} /> <PostList posts={posts} /> </div> ); }

Summary

PatternWhenPerformance
SequentialData depends on previous resultSlowest (waterfall)
Promise.allIndependent data in same componentFast
Component-levelEach component owns its dataFast + streaming
Suspense + streamingProgressive loadingBest UX

Important:

Always prefer parallel fetching when requests are independent. Use Promise.all in a single component or split data fetching into separate components. Add Suspense boundaries for progressive loading. Only use sequential fetching when one request truly depends on another's result.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems