Verified against Claude Code · 2026-07-28
Restructure sequential awaits in a Server Component into real parallel fetches
Finds independent data requests being awaited one after another inside a Server Component and restructures them to start concurrently, without breaking the ones that genuinely depend on an earlier result.
The prompt
Ready to copy — highlighted parts are example details you can swap.
You are restructuring data fetching inside a Server Component to eliminate a request waterfall — one fetch's await blocking the start of the next fetch when the two don't actually depend on each other's result. COMPONENT CODE An async OrderDetailPage component that awaits getOrder(id), then awaits getCustomer(order.customerId), then awaits getShippingStatus(id), with getShippingStatus not actually needing anything from the first two calls. DATA DEPENDENCIES getCustomer needs order.customerId from the first call, so it's genuinely sequential; getShippingStatus only needs the order id, which is already known upfront, so it doesn't need to wait on either of the other two FRAMEWORK CONTEXT Server Component in the App Router; getOrder averages 80ms, getCustomer averages 60ms, getShippingStatus (an external carrier API) averages 400ms RESTRUCTURING RULES Identify every fetch or data call in this component and classify each as either independent — its inputs don't depend on any other fetch in this component resolving first — or genuinely dependent, meaning it needs a value produced by an earlier fetch's result, such as an ID it doesn't have until a prior lookup returns. For every group of independent fetches currently written as sequential awaits, one after another, restructure them to start together: call all of them first without awaiting immediately, then await the results, either individually where each result is used at a different point in the component, or together with a helper like Promise.all when they're consumed together. Do not force a fetch that's genuinely dependent on an earlier one into this pattern — if fetch B needs a value only fetch A's result provides, sequential is not a mistake there, and restructuring it to look parallel would either be impossible or would require guessing at a value fetch A hasn't produced yet. Where the component passes fetched data down to a child component that also fetches, check whether the child's fetch could have started earlier — for instance, by having the parent start the request and pass a promise down rather than the awaited value, letting the child await it independently — rather than gating the child fetch's start entirely behind the parent finishing its own render. Note explicitly where Next.js's request memoization already deduplicates a repeated fetch call across this component tree, since restructuring should not accidentally introduce a second, differently-shaped request for data another part of the tree already requested with the exact same arguments — that's not a fix, it's a second cache-miss disguised as an optimization. OUTPUT FORMAT The restructured component code, followed by one line naming which fetches were genuinely sequential and were left as such, and an estimate of the wall-clock time saved by parallelizing the rest, given the approximate latency of each fetch if that's known.
Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
A request waterfall in a Server Component is easy to write by accident because the code that produces it looks completely ordinary — a sequence of await calls reads as a natural, linear description of what the component needs, and nothing about the syntax signals that two of those awaits could have started at the same moment instead of one after the other; the bug is entirely about timing, not correctness, so it never shows up as an error, only as a slower page than the code's own logic required. Classifying each fetch as independent or genuinely dependent before touching any code is what prevents the two most common overcorrections in either direction: parallelizing something that actually needs an earlier result, which either breaks outright or silently uses a stale or missing value, and leaving something sequential out of caution when it never depended on anything at all, which just leaves the original waterfall in place under the guise of being careful. The pass-a-promise-down technique for child components targets a subtler version of the same problem: a parent that awaits its own data fully before rendering a child effectively delays the start of the child's fetch until the parent's fetch has completely finished, even when the child's fetch has nothing to do with the parent's data — starting the child's request as early as possible and letting it resolve independently, on its own schedule, is what actually collapses two sequential network round trips into one overlapping window instead of two consecutive ones. Flagging Next.js's request memoization explicitly matters because a restructuring pass focused purely on parallelizing awaits can, without meaning to, introduce a fetch call shaped slightly differently from an identical one elsewhere in the tree — different argument order, an extra option key — which defeats memoization's automatic deduplication and turns what should have been one shared request into two separate ones, adding a request rather than removing a delay.
What you get back
async function OrderDetailPage({ id }) { const orderPromise = getOrder(id); const shippingPromise = getShippingStatus(id); // independent of order/customer, started immediately const order = await orderPromise; const [customer, shipping] = await Promise.all([getCustomer(order.customerId), shippingPromise]); // getCustomer is genuinely sequential (needs order.customerId) — left as-is, just no longer blocking shipping's start return (/* render using order, customer, shipping */); } Sequential (left as-is): getCustomer, since it needs order.customerId. Parallelized: getShippingStatus now starts alongside getOrder instead of after both getOrder and getCustomer resolve. Estimated savings: roughly 400ms — getShippingStatus's ~400ms latency now overlaps with getOrder and getCustomer's combined ~140ms instead of adding on top of it.
Verified against
Claude Code Sonnet 4.6 · 2026-07-28
Cursor Cursor 2.1 · 2026-08-04
Changelog
- 2026-07-28 — Initial publish, verified against Claude Code (Sonnet 4.6) and Cursor 2.1 on a Next.js 16 order-detail Server Component.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
