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:
// ❌ 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:
// ✅ 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:
// 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:
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:
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
| Pattern | When | Performance |
|---|---|---|
| Sequential | Data depends on previous result | Slowest (waterfall) |
Promise.all | Independent data in same component | Fast |
| Component-level | Each component owns its data | Fast + streaming |
| Suspense + streaming | Progressive loading | Best 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 readyA concise answer to help you respond confidently on this topic during an interview.