DEV Community

Cover image for Parallel vs Sequential Data Fetching in Next.js 15 with TypeScript
Arfatur Rahman
Arfatur Rahman

Posted on

Parallel vs Sequential Data Fetching in Next.js 15 with TypeScript

Introduction

Data fetching is central to modern web development, especially in performance-critical applications. With the release of Next.js 15, developers can take full advantage of server components and advanced data-fetching patterns like sequential and parallel fetching to streamline performance and user experience.

This article explores both patterns with real-world examples, TypeScript usage, and analysis of when to use each approach.

What is Sequential Data Fetching?

Sequential fetching refers to fetching data one after another, where each fetch waits for the previous one to complete. This is helpful when the second dataset depends on the result of the first.

Example of Sequential Fetching in Next.js

This Next.js example showcases sequential data fetching: the page first loads all post titles, and then, for each individual post, an AuthorComponent is rendered. Crucially, each AuthorComponent introduces a simulated 3-second delay before fetching author details, and is wrapped in React's Suspense to display a "Loading author..." fallback. This results in a user experience where post titles appear quickly, followed by individual author placeholders that progressively resolve into names as each author's data (and the simulated delay) completes, providing a staggered loading effect.

// page.tsx
const PostSequential = async () => {
  const posts = await fetch(`https://jsonplaceholder.typicode.com/posts`).then(res => res.json());
  return (
    <div>
      {posts.map(post => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <Suspense fallback={<span>Loading author...</span>}>
            <AuthorComponent id={post.id} />
          </Suspense>
        </div>
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode
const AuthorComponent = async ({ id }: { id: number }) => {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  const user: UserProps = await fetch(
    `https://jsonplaceholder.typicode.com/users/${id}`
  ).then((res) => res.json());
  return <p className="text-sm text-gray-500 mt-4">Author: {user?.name}</p>;
};
Enter fullscreen mode Exit fullscreen mode
βœ… Advantages ❌ Disadvantages
πŸ”„ Maintains dependency order 🐌 Slower overall loading times
🧩 Easier debugging for dependent logic πŸ’§ Creates "waterfalls" in rendering
🧠 Ideal for conditional or step-wise fetching πŸ˜• Poor user experience for independent data
πŸ› οΈ Better control over fetch flow and logic πŸ“‰ Inefficient resource usage (wasted wait time)
πŸ” Useful when one fetch relies on previous data πŸ“΅ Can't leverage concurrent server resources
πŸ“‹ Simplifies state tracking in some cases πŸŒ€ Can lead to nested logic and complexity

Advantages of Sequential Fetching

  • Maintains dependency order
  • Easier debugging for dependent logic
  • Ideal for conditional fetching

Disadvantages of Sequential Fetching

  • Slower overall loading times
  • Creates waterfalls in rendering
  • Poor user experience for independent data

What is Parallel Data Fetching?

Parallel fetching initiates multiple data requests at the same time, improving performance by reducing wait times. Ideal for fetching independent data.

Example of Parallel Fetching in Next.js

In this example, the UserParallel server component demonstrates parallel data fetching by simultaneously initiating two asynchronous calls: getUserAlbums() and getUserPosts(), both of which include a simulated 3-second delay. Instead of waiting for each fetch sequentially (which would total ~6 seconds), the component uses Promise.all() to execute both functions in parallel, reducing the overall wait time to just ~3 seconds. This improves performance significantly, especially when fetching multiple independent resources. Once both promises resolve, the UI displays the albums and posts side-by-side in a responsive layout. This pattern is ideal when the fetched data is independent and can be rendered concurrently, ensuring better responsiveness and user experience.

import React from "react";

// Define the types for your data
type PostProps = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

type AlbumProps = {
  userId: number;
  id: number;
  title: string;
};

// Define the props for the UserParallel component
type UserParallelProps = {
  params: Promise<{ userId: string }>; // Assuming params is directly an object, common in Next.js
};

const UserParallel = async ({ params }: UserParallelProps) => {
  const { userId } = await params; // Access userId directly from params

  // Function to fetch user albums with proper URL and error handling
  const getUserAlbums = async (id: string): Promise<AlbumProps[]> => {
    try {
      await new Promise((resolve) => setTimeout(resolve, 3000));
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/albums?userId=${id}`
      );
      if (!res.ok) {
        throw new Error(`Failed to fetch albums: ${res.statusText}`);
      }
      return res.json();
    } catch (error) {
      console.error("Error fetching albums:", error);
      return []; // Return an empty array on error
    }
  };

  // Function to fetch user posts with proper URL and error handling
  const getUserPosts = async (id: string): Promise<PostProps[]> => {
    try {
      await new Promise((resolve) => setTimeout(resolve, 3000));
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/posts?userId=${id}`
      );
      if (!res.ok) {
        throw new Error(`Failed to fetch posts: ${res.statusText}`);
      }
      return res.json();
    } catch (error) {
      console.error("Error fetching posts:", error);
      return []; // Return an empty array on error
    }
  };

  // Initiate both fetch calls in parallel
  const albumDataPromise = getUserAlbums(userId);
  const postsDataPromise = getUserPosts(userId);

  // Wait for both promises to resolve
  const [albums, posts] = await Promise.all([
    albumDataPromise,
    postsDataPromise,
  ]);

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4">
      <div className="bg-gray-100 p-4 rounded-lg shadow-md">
        <h2 className="text-2xl font-bold mb-4">Albums</h2>
        {albums.length > 0 ? (
          <ul>
            {albums.map((album) => (
              <li key={album.id} className="mb-2 p-2 bg-white rounded-md">
                <p className="font-semibold">{album.title}</p>
              </li>
            ))}
          </ul>
        ) : (
          <p>No albums found for this user.</p>
        )}
      </div>

      <div className="bg-gray-100 p-4 rounded-lg shadow-md">
        <h2 className="text-2xl font-bold mb-4">Posts</h2>
        {posts.length > 0 ? (
          <ul>
            {posts.map((post) => (
              <li key={post.id} className="mb-2 p-2 bg-white rounded-md">
                <h3 className="text-lg font-semibold">{post.title}</h3>
                <p className="text-sm text-gray-700">{post.body}</p>
              </li>
            ))}
          </ul>
        ) : (
          <p>No posts found for this user.</p>
        )}
      </div>
    </div>
  );
};

export default UserParallel;

Enter fullscreen mode Exit fullscreen mode
βœ… Advantages ❌ Disadvantages
⚑ Faster overall data retrieval πŸ” Harder to manage data dependencies
⏱️ Reduced latency leads to better user experience πŸ“¦ Cannot conditionally skip unnecessary fetches
🧠 Ideal for independent or unrelated datasets 🧩 More complex error handling and coordination
πŸ› οΈ Utilizes server resources efficiently πŸ” Race conditions possible without proper guards
πŸš€ Enables true concurrent rendering with <Suspense> πŸ§ͺ Testing and debugging can be more difficult
πŸ“Š Improves perceived performance (UI loads quicker) πŸ•΅οΈ Harder to trace fetch flow in deeply nested trees

Advantages of Parallel Fetching

  • Faster overall data retrieval
  • Reduced latency and better UX
  • Best for independent datasets

Disadvantages of Parallel Fetching

  • Harder to manage dependencies
  • Cannot conditionally skip unnecessary fetches

Key Differences Table

🧩 Feature πŸ”„ Sequential Fetching ⚑ Parallel Fetching
Execution Order One-by-one, in strict sequence Simultaneous, non-blocking
Performance Slower overall, due to request chaining Faster, better utilization of async behavior
Use Case When later data depends on earlier fetches When fetches are independent
Logic Simplicity Easier to understand and debug Slightly more complex (due to Promise.all, race conditions)
Error Handling Easier to isolate and handle errors Needs grouped or coordinated error strategies
UX Impact Slower perceived loading, especially with nested fetches Snappier UI, especially for rendering sections independently
Server Resource Utilization Less efficient, uses time serially More efficient, uses resources concurrently
Conditional Fetch Support Excellent, supports if-else/early returns Limited, harder to cancel or skip once started
Suitability with <Suspense> Works well for step-by-step reveals Ideal for loading independent UI chunks in parallel
Scalability for Large Pages May become bottlenecked with many dependent calls Scales better with many independent calls

Best Practices from Official Docs

From the Next.js documentation:

  • Use server components for secure, backend-close data fetching.
  • Memoized fetch ensures no redundant calls.
  • Leverage Suspense for lazy-loading nested data.
  • Consider loading.js or Promise.all() for optimal UX.

Real-World Use Cases

Sequential Use Case

A user profile page where the second API call depends on the user ID fetched from the first call.

Parallel Use Case

A dashboard showing metrics, charts, and notifications from multiple endpoints.

Performance Considerations

  • Always prefer parallel fetching for independent data.
  • Use cache() and preload patterns when possible.
  • Combine Suspense and server components for fine-grained control.

When to Use Which?

Use sequential when:

  • There is a data dependency chain.
  • You want to avoid redundant calls.

Use parallel when:

  • Fetching unrelated data.
  • You aim for fast, efficient rendering.

Conclusion

Understanding the trade-offs between sequential and parallel data fetching in Next.js 15 can greatly impact application performance and user experience. By identifying dependencies and structuring fetch logic accordingly, you ensure optimal rendering and responsiveness.

FAQs

Q1: Can I mix sequential and parallel fetching in the same component?
Yes. You can initiate some fetches in parallel and others sequentially based on dependencies.

Q2: Does fetch() in Next.js automatically cache data?
Yes. In server components, fetch is memoized and reused to prevent redundant requests.

Q3: How does Suspense help in sequential fetching?
It allows streaming parts of the UI while waiting for slower components to resolve data.

Q4: Are these patterns only for server components?
They work best in server components, but client components can also use similar patterns with client libraries.

Q5: Is Promise.all safe for all cases in parallel fetching?
Only if the fetches are independent. Otherwise, it can cause errors or wasted resources.

About the Author

Hi, I’m Arfatur Rahman, a Full-Stack Developer from Chittagong, Bangladesh, specializing in AI-powered applications, RAG-based chatbots, and scalable web platforms. I’ve worked with tools like Next.js, LangChain, OpenAI, Azure, and Supabase, building everything from real-time dashboards to SaaS products with payment integration. Passionate about web development, vector databases, and AI integration, I enjoy sharing what I learn through writing and open-source work.

Connect with me:

🌐 Portfolio

πŸ’Ό LinkedIn

πŸ‘¨β€πŸ’» GitHub

✍️ Dev.to

πŸ“š Medium

Top comments (0)

πŸ‘‹ Kindness is contagious

Take a moment to explore this thoughtful article, beloved by the supportive DEV Community. Coders of every background are invited to share and elevate our collective know-how.

A heartfelt "thank you" can brighten someone's dayβ€”leave your appreciation below!

On DEV, sharing knowledge smooths our journey and tightens our community bonds. Enjoyed this? A quick thank you to the author is hugely appreciated.

Okay