Suggest an editImprove this articleRefine the answer for “Parallel and sequential data fetching in Next.js”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Parallel data fetching in Next.js** starts multiple requests at once and waits for the slowest one to finish. Sequential runs one at a time, blocking until each resolves. ```tsx // Sequential: 200ms + 300ms = 500ms total const user = await getUser(id); const posts = await getPosts(id); // Parallel: max(200ms, 300ms) = 300ms total const [user, posts] = await Promise.all([getUser(id), getPosts(id)]); ``` **Key point:** Server Components do not parallelize automatically. Use `Promise.all()` explicitly for independent requests.Shown above the full answer for quick recall.Answer (EN)Image**Parallel data fetching** starts all requests at once and waits for the slowest one to finish; sequential fetching waits for each request to complete before starting the next. ## Theory ### TL;DR - Sequential = waterfall: 200ms + 300ms + 200ms = 700ms total - Parallel = `Promise.all()`: max(200ms, 300ms, 200ms) = 300ms total - Use parallel when requests are independent; use sequential when later data depends on earlier results - `Promise.all()` rejects if any request fails; use `Promise.allSettled()` when partial failures are acceptable - Server Components in Next.js do not parallelize requests automatically - you have to structure the code yourself ### Quick example ```tsx // Sequential: all durations add up const user = await getUser(id); // 200ms const posts = await getPosts(id); // 300ms (starts after user finishes) const comments = await getComments(id); // 200ms (starts after posts finish) // Total: ~700ms // Parallel: only the slowest request matters const [user, posts, comments] = await Promise.all([ getUser(id), // 200ms getPosts(id), // 300ms getComments(id), // 200ms - all three start at the same time ]); // Total: ~300ms ``` Sequential adds every duration. Parallel takes only the longest. On a page with 5-6 independent requests, that difference becomes very visible to users. ### Key difference Sequential fetching creates a dependency chain where each `await` blocks the entire render pipeline until that request resolves. Parallel fetching creates all promises immediately and waits for the last one to settle. The time savings compound at scale: three 300ms requests take 900ms sequentially but only 300ms in parallel. Network latency is usually the bottleneck in server-side rendering, so this matters a lot. ### When to use - Parallel when requests are independent (user profile, posts, followers - none need each other's data) - Sequential when later data depends on earlier results (fetch user first, then fetch the user's team using `user.teamId`) - Mixed when some requests are independent and some are not (parallelize everything that shares the same ID, then fetch dependent data sequentially) - Streaming with Suspense when you want to show partial UI while slower data loads in the background ### Comparison table | Pattern | Total time | Dependencies | Typical use case | |---------|-----------|--------------|------------------| | Sequential | Sum of all requests | Each depends on previous | Hierarchical data: user → posts → comments | | Parallel (`Promise.all`) | Max of all requests | None | Independent data: profile, analytics, notifications | | Mixed | Optimized | Some dependencies | Most real apps | | Streaming (Suspense) | Progressive | Flexible | Show UI incrementally | ### How it works internally When you call `Promise.all([...])`, JavaScript creates all promises immediately and they start executing right away. Then the async function suspends until the last one settles. The event loop stays free for other work. In Next.js Server Components, this all happens during server render, and the client receives fully rendered HTML. One thing I notice regularly: developers add `Promise.all()` and assume they are done. But if there is even one `await` before calling `Promise.all()`, a sequential bottleneck already exists. The code looks parallel but is not. ### Common mistakes **Mistake 1: Waterfall hidden inside what looks like parallel code** ```tsx // Wrong: reviews still waits for product to resolve const product = await getProduct(productId); const [reviews, recommendations] = await Promise.all([ getReviews(product.id), // waits for product first getRecommendations(productId) ]); // Correct: start all promises before awaiting anything const productPromise = getProduct(productId); const reviewsPromise = getReviews(productId); const recommendationsPromise = getRecommendations(productId); const [product, reviews, recommendations] = await Promise.all([ productPromise, reviewsPromise, recommendationsPromise ]); ``` **Mistake 2: `Promise.all()` fails on any rejection** ```tsx // Wrong: one failure loses user and posts too const [user, posts, comments] = await Promise.all([ getUser(id), getPosts(id), getComments(id) // one failure crashes everything ]); // Correct: allSettled() for non-critical data const results = await Promise.allSettled([ getUser(id), getPosts(id), getComments(id) ]); const user = results[0].status === 'fulfilled' ? results[0].value : null; const posts = results[1].status === 'fulfilled' ? results[1].value : []; const comments = results[2].status === 'fulfilled' ? results[2].value : []; ``` **Mistake 3: Sequential fetching inside loops (N+1 problem)** ```tsx // Wrong: fetches one user at a time const userIds = [1, 2, 3, 4, 5]; const users = []; for (const id of userIds) { const user = await getUser(id); // 5 * 200ms = 1000ms users.push(user); } // Correct: all five requests fire at once const users = await Promise.all( userIds.map(id => getUser(id)) // ~200ms total ); ``` **Mistake 4: Thinking Server Components parallelize automatically** ```tsx // Wrong: still sequential even in a Server Component export default async function Page() { const user = await getUser(id); const posts = await getPosts(id); // waits for user for no reason return <div>{/* ... */}</div>; } // Correct: explicitly parallel export default async function Page() { const [user, posts] = await Promise.all([ getUser(id), getPosts(id) ]); return <div>{/* ... */}</div>; } ``` Next.js does not reorder your `await` calls. Parallelism is your responsibility. **Mistake 5: Passing already-resolved values to `Promise.all()`** ```tsx // Wrong: each request already ran sequentially before Promise.all const user = await getUser(id); const posts = await getPosts(id); return Promise.all([user, posts]); // these are values, not promises // Correct: pass the promises themselves return Promise.all([getUser(id), getPosts(id)]); ``` ### Real-world usage - Next.js dashboard pages: fetch user profile, analytics, and notifications in parallel before rendering - React Query: `useQueries()` runs multiple queries in parallel; `useQuery()` runs one at a time - GraphQL: single HTTP request where multiple fields resolve in parallel on the server - Express middleware: `Promise.all([checkAuth(), checkRateLimit(), logRequest()])` runs checks together - Database queries: `Promise.all([db.users.find(), db.posts.find()])` instead of sequential calls ### Follow-up questions **Q:** When would you use `Promise.allSettled()` instead of `Promise.all()`? **A:** When one failure should not block everything else. `Promise.all()` rejects immediately if any promise rejects. `allSettled()` waits for all of them and returns each result with a status. Use it for non-critical data like recommendations or analytics, where a fetch failure should show a fallback rather than crash the page. **Q:** Can there be a waterfall inside a parallel fetch? **A:** Yes. `await Promise.all([getUser(id), getPosts(user.id)])` - the second request still needs `user` to resolve before it can start. The fix: start all promises before `Promise.all()`, or restructure so `getPosts()` takes a shared ID that does not depend on a prior fetch. **Q:** How does Suspense streaming change the decision between parallel and sequential? **A:** With Suspense you can show partial UI while slow data loads separately. Fetch critical data like user profile in parallel with everything else, then stream non-critical parts like recommendations behind a Suspense boundary. You get the speed of parallel with the UX of progressive rendering. **Q:** You have 10 independent API calls and one consistently takes 3 seconds. How do you optimize without changing the API? **A:** Run all 10 in parallel. Show the 9 fast results through Suspense boundaries while the slow one streams in. Alternatively, use `Promise.allSettled()` with a timeout wrapper to show a fallback after a threshold. The goal is to not let one slow request block the rest of the page. **Q:** How do you verify that your fetches are actually parallel? **A:** Open DevTools Network tab and look at the timeline. Parallel requests overlap; sequential ones start only after the previous one ends. On the server, add `console.time()` / `console.timeEnd()` around `Promise.all()` and compare the total time to the sum of individual requests. ## Examples ### Basic: sequential vs parallel in a Server Component ```tsx // Sequential: posts waits for user even though it does not need its data export default async function ProfilePage({ userId }: { userId: string }) { const user = await getUser(userId); // 200ms const posts = await getUserPosts(userId); // 300ms - could have started earlier // Total: ~500ms return <div><h1>{user.name}</h1><PostList posts={posts} /></div>; } // Parallel: both start at the same time export default async function ProfilePage({ userId }: { userId: string }) { const [user, posts] = await Promise.all([ getUser(userId), // 200ms getUserPosts(userId), // 300ms - starts immediately ]); // Total: ~300ms return <div><h1>{user.name}</h1><PostList posts={posts} /></div>; } ``` Both fetch the same data. The parallel version is 200ms faster. Add more independent requests and the gap keeps growing. ### Intermediate: dashboard with three independent data sources ```tsx // Next.js Server Component - all three requests start at the same time export default async function Dashboard({ userId }: { userId: string }) { const [user, analytics, notifications] = await Promise.all([ db.user.findUnique({ where: { id: userId } }), db.analytics.getUserStats(userId), db.notifications.getUnread(userId) ]); // None of these depend on each other - Promise.all makes that explicit return ( <div> <UserCard user={user} /> <AnalyticsChart data={analytics} /> <NotificationBell count={notifications.length} /> </div> ); } ``` None of these requests need each other's data. There is no reason to wait for `user` before fetching analytics. `Promise.all()` makes the intent clear: all three are independent. ### Advanced: mixed pattern with dependent and independent requests ```tsx export default async function ProductPage({ productId }: { productId: string }) { // Start all three promises immediately. // reviews and recommendations only need productId, which we already have. const productPromise = getProduct(productId); const reviewsPromise = getReviews(productId); const recommendationsPromise = getRecommendations(productId); const [product, reviews, recommendations] = await Promise.all([ productPromise, reviewsPromise, recommendationsPromise ]); // Seller data needs product.sellerId - sequential here is intentional const seller = await getSeller(product.sellerId); return ( <div> <ProductCard product={product} seller={seller} /> <ReviewList reviews={reviews} /> <RecommendationGrid items={recommendations} /> </div> ); } ``` Starting promises before `Promise.all()` is the key pattern. If the first line had been `const product = await getProduct(productId)`, reviews and recommendations would have waited an extra 300ms for no reason. The sequential fetch for `seller` at the end is intentional and correct - it genuinely needs `product.sellerId`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.