Data and rendering

Stream useful loading UI

Use loading.tsx and focused Suspense boundaries so slow data does not leave navigation without feedback.

Server rendering can stream route output. A loading.tsx file gives the route segment an immediate fallback while its page is prepared.

Use a route-level loading file for a broad page skeleton. Use <Suspense> closer to a slow component when the rest of the page can render first. Make the fallback resemble the space it will replace so the page does not jump when content arrives.

The route fallback is prefetched during navigation and wraps the page below its shared layout. That lets navigation feel immediate while the server finishes the segment. On a direct request, streaming still depends on the server and hosting path delivering chunks rather than buffering the entire response.

Prefer the narrowest meaningful boundary. If only the recent-notes list is slow, keep the heading and create button outside Suspense so useful work stays visible. Several tiny boundaries can create visual noise and a waterfall, so split by user-visible units rather than by every async function.

// app/notes/loading.tsx
export default function Loading() {
  return <p aria-live="polite">Loading notes…</p>
}

Add an intentional delay in your notes data function, create loading.tsx, and navigate to /notes through a Link. You should briefly see Loading notes… while the layout heading stays put. Remove the delay and the fallback should barely flash.

Replace the broad route fallback with Suspense around only the slow list. Compare client navigation and a direct load. Each fallback should reserve the right amount of layout space.

If you skip loading UI entirely, a slow query leaves the previous page on screen with no feedback. Users think the click did nothing.

Artificial delays belong only in local development. Remove them before you measure real performance or demo the app to someone else.

Lesson completed