Next.js

Verified against Claude Code · 2026-07-30

Design loading UI so one slow query doesn't block a whole page from painting

Places loading.tsx and granular Suspense boundaries around a page's actual data dependencies so fast content streams in immediately, instead of waiting on the single slowest fetch on the page.

Claude CodeCursorv0 by Vercel3 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

You are designing the loading UI for a specific page using route-level loading.tsx and granular Suspense boundaries, so the page streams in as each piece of data resolves rather than showing one full-page spinner until the single slowest fetch finishes.

PAGE DATA DEPENDENCIES
user profile header (fast, cached), order history table (medium), real-time inventory sync status (slow, external API)

SLOWEST DATA SOURCE
The inventory sync status — it calls a third-party API with roughly 2 seconds of typical latency

DATA FETCHING APPROACH
Async Server Components fetching directly, with each section as its own async function component

REQUIREMENTS
Use loading.tsx at the route segment level for the initial navigation fallback — this file automatically wraps the entire segment in a Suspense boundary, which is exactly why it should show a skeleton that roughly matches the eventual layout rather than a generic centered spinner, since it's the very first thing a visitor sees on that route. Identify which pieces of the page are genuinely independent of each other's data, meaning neither needs to wait on the other, and wrap each independent piece in its own Suspense boundary with its own fallback so each can resolve and stream in on its own schedule. The slowest data source named above must never be awaited at the top of the page component in a way that blocks everything else on the page from rendering — its fetch should start as early as possible, but nothing else on the page should be gated behind its resolution. Give each Suspense boundary a fallback that's sized and shaped like the real content it's replacing — a skeleton with matching approximate dimensions, not a generic centered spinner that causes a visible layout jump the moment the real content swaps in and claims a different amount of space. If a section legitimately needs to wait on another — a detail panel that needs to know which item was selected before it can fetch anything — don't force it into a parallel Suspense boundary alongside unrelated content just for the sake of consistency; nest it inside the boundary it genuinely depends on, or leave it sequential, and say so explicitly rather than parallelizing something that has a real, unavoidable data dependency. Note explicitly where this interacts with caching, since the two are easy to conflate: a Suspense boundary around a component changes only when the browser sees that component's result, not whether the underlying data is cached — caching is a separate decision made through the data-fetching approach itself, not something Suspense boundary placement affects one way or the other.

OUTPUT FORMAT
The page's Suspense structure as a component tree, described in text with fallback components sketched rather than fully styled, plus a one-line note on which sections are genuinely independent versus genuinely sequential and why each was placed the way it was.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

The mechanism worth naming explicitly is that loading.tsx isn't just a fallback file that happens to render while data loads — it automatically wraps the entire route segment in a Suspense boundary, which is exactly why a single loading.tsx at the page level still produces an all-or-nothing spinner if nothing inside the page has its own, more granular boundaries beneath it: the page-level boundary resolves only when every async component inside it has resolved, so the slowest one still gates the whole segment even though loading.tsx looks, on the surface, like it's already solving the streaming problem. The actual fix is structural, not decorative: identifying which sections are truly independent and giving each its own Suspense boundary means the fast profile header can paint and become interactive immediately while the slow third-party inventory check is still pending, instead of the entire page waiting on whichever promise in the tree happens to resolve last, regardless of how unrelated that slow section is to what the visitor actually came to see. Explicitly separating "independent — parallelize" from "genuinely sequential — don't force it" stops the model from over-applying Suspense boundaries to sections that have a real data dependency on each other, which would just add waterfall complexity — a detail panel nested in its own boundary before it even knows which item was selected has nothing useful to fetch yet, so wrapping it in a parallel boundary anyway adds structure without adding any of the streaming benefit that structure is supposed to buy. And calling out that Suspense affects only when content is shown, not whether it's cached, prevents conflating two decisions that are easy to blur together in practice but are actually orthogonal: a component can be wrapped in a tight, well-placed Suspense boundary and still refetch uncached data on every request, and a build that only gets the boundary placement right without checking the caching strategy separately will still feel slow, just with better-organized loading states around the slowness.

What you get back

Suspense tree: <ProfileHeader> resolves fast and renders without its own boundary if it's genuinely not a bottleneck anywhere in testing. A Suspense boundary around <OrderHistory> (fallback: OrderHistorySkeleton, matching the table's approximate row height) and a separate one around <InventoryStatus> (fallback: InventoryStatusSkeleton) sit as siblings, so the order history table appears well before the roughly 2-second inventory check resolves, instead of both being gated behind whichever one happens to be slower. Independent: OrderHistory and InventoryStatus don't share any data. Sequential: none on this page — if a future detail panel is added that depends on a selected order, it should nest inside OrderHistory's boundary rather than sit as a sibling.

Verified against

Claude Code Sonnet 4.6 · 2026-07-30

v0 by Vercel 2026.7 · 2026-08-06

Changelog

  • 2026-07-30 — Initial publish, verified against Claude Code (Sonnet 4.6) and v0 by Vercel on a Next.js 16 dashboard page with an external API dependency.

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
All Next.js prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY