Next.js: The React Framework (App Router)Entry 7 of 16
Loading UI and Streaming
loading.tsx shows instant fallback UI while a route's data loads. <Suspense> does the same for any part of a page, and the server streams each part as it's ready.
react.dev<Suspense>
nextjs.orgloading.js
Notes
loading.tsxwraps the segment'spage.tsxin<Suspense>automatically.- Wrap slow, independent sections in their own
<Suspense>so the fast parts show first. - Streaming means the browser receives the shell immediately, then the HTML for each suspended part as its data resolves.
What / Why / When
- What
Fallback UI plus progressive HTML.
- Why
The user sees the page immediately instead of a blank screen while the slowest query finishes.
- When
Any route with slow data, or pages with one slow section.
- Use case
A post page whose comments load slowly.
Code compare
// app/posts/loading.tsxexport default function Loading() { return <p>Loading posts…</p>;}// app/posts/[slug]/page.tsximport { Suspense } from "react";import { Comments } from "./comments"; // async Server Componentexport default async function PostPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; const post = await getPost(slug); return ( <article> <h1>{post.title}</h1> <div>{post.body}</div> <Suspense fallback={<p>Loading comments…</p>}> <Comments slug={slug} /> </Suspense> </article> );}@defer loads a component's code lazily and shows @loading / @placeholder blocks meanwhile. Server-side, deferred blocks render their placeholder unless you use incremental hydration:
<article> <h1>{{ post().title }}</h1> <div>{{ post().body }}</div> @defer (on viewport) { <app-comments [slug]="slug()" /> } @placeholder { <p>Comments</p> } @loading (minimum 300ms) { <p>Loading comments…</p> }</article>For loading state of the data itself, use the resource's status: @if (post.isLoading()) { … }.
Differences and gotchas
- Next.js streams data;
@deferdefers code (and the work inside it). Angular SSR waits for pending HTTP requests before sending HTML; it doesn't stream sections. loading.tsxis per route segment;@deferis per template block.
Quick reference
| Next.js | Angular |
|---|---|
loading.tsx | @if (resource.isLoading()) / route-level spinner |
<Suspense fallback> | @defer { } @loading { } @placeholder { } |
| HTML streaming | Not supported; SSR waits for app to be stable |