Sharing is caring!

A few weeks ago I opened a dashboard a friend built and watched the network tab with growing pain. Fourteen requests, one after another, each waiting for the previous. Spinners inside spinners. The page took nine seconds to become usable – on fiber. Every single request was avoidable. This post is the sequel to our React styling guide, and it covers the other half of modern React: getting data to the screen without the waterfall.

31 43006 a waterfall - React Data Fetching in 2026: Stop the Waterfall

The short answer

Fetch on the server by default. Fetch on the client only for things that are interactive by nature: search boxes, filters, and mutations. If your component fetches inside useEffect on mount in 2026, that is tech debt. Full stop.

What a waterfall actually looks like

The classic pattern: component mounts, fires a request for the user, waits, then fires a request for that user orders, waits, then fires a request for each order details. Each round trip blocks the next. On localhost you never notice – 5ms per hop. In production, with 200ms per hop across three chained requests, your user stares at a skeleton for over half a second before anything real shows. Multiply by nested components and you get the nine-second dashboard.

useEffect(() => {
  fetch('/api/user').then(u =>
    fetch('/api/orders?user=' + u.id).then(os =>
      os.forEach(o => fetch('/api/details?order=' + o.id))
    )
  );
}, []); // each fetch waits for the previous. Don't do this.

Approach 1: Async Server Components (the default now)

Make the component itself async and fetch before anything ships to the browser. The HTML arrives with data already in it. No spinners, no waterfall, no client JavaScript spent on fetching:

async function Orders() {
  const res = await fetch('https://api.shop.com/orders',
    { next: { revalidate: 60 } });
  const orders = await res.json();
  return <Table rows={orders} />;
}

The revalidate: 60 is doing quiet heavy lifting: the result is cached for a minute, so a thousand visitors trigger one upstream request, not a thousand. I have seen this single change cut API bills in half.

32 43006 b servercore - React Data Fetching in 2026: Stop the Waterfall

Approach 2: Suspense boundaries that do not block the page

Not everything resolves at the same speed, and that is fine. Wrap each slow section in its own Suspense boundary with a skeleton that matches the final layout. The page streams: header and navigation first, then each section pops in as its data lands. The mistake I keep seeing is one giant Suspense around the whole page – that is just a full-page spinner with extra steps. One boundary per section. Always.

Approach 3: Client fetching where it actually belongs

Some things genuinely live on the client. A search-as-you-type box cannot be a server round trip per keystroke – debounce it and fetch on input. Filters, sorting, optimistic mutations (like a tweet button that flips instantly and syncs after) – all client. The rule I use: if the user did something to cause it, fetch on the client. If the page needs it to exist at all, fetch on the server.

The verdict: my rule

Server first, Suspense per section, client only for interaction. When I review React code now, I search for useEffect calls containing fetch – every hit is a conversation. Nine times out of ten it moves to the server in five minutes and a whole class of loading bugs disappears with it.

ApproachFetch whenWatch out
Server ComponentsThe page needs the data to existNeeds a framework with server support
Suspense per sectionSections resolve at different speedsOne giant boundary is a full-page spinner
Client fetchThe user caused it: search, filters, mutationsNever on mount for initial content
Client cache libraryDashboards with constant mutationsOverkill for content sites and stores
33 43006 c skeleton - React Data Fetching in 2026: Stop the Waterfall

Senior addendum: caching without tears

Two things worth knowing once the basics are in place. First, duplicate fetch calls in the same render are automatically deduplicated – call it in five components, it fires once. Second, reach for a client cache library (React Query and friends) only for dashboards with constant mutations, where data goes stale while you look at it. For content sites and stores, server cache plus revalidation wins and there is dramatically less code to maintain.

Bottom line

Part one of this series fixed how React looks. This part fixes how fast it feels: server by default, stream by section, client only for interaction. Do that and the waterfall simply has nowhere to form. Next in the series: the styling + data combo – building a real dashboard page end to end.

Part of our React in 2026 series.

Hello, my name is Mahmoud Ameara — senior front-end developer and designer, and the founder of mameara.com. Here I share design resources, tutorials, freebies, and practical frontend guides.
mameara © 2010 – 2026