Next.js Streaming and Suspense
Some pages need data that arrives slowly. A product page might wait on a reviews service, a recommendation engine, and an inventory system. Without streaming, the visitor stares at a blank screen until the slowest request finishes. Streaming sends the page in pieces, so visitors see useful content right away while slower parts load in the background.
The Everyday View
Think about a restaurant meal. A bad kitchen holds every dish until the slowest one is ready and serves everything together. A good kitchen sends the bread immediately, follows with the salad, and brings the main course when it finishes cooking. Guests stay happy because something always arrives.
Streaming applies the good-kitchen approach to web pages.
Without Streaming and With Streaming
WITHOUT STREAMING Time ----------------------------------------------> Server [wait for header][wait for reviews][wait for deals] Browser [shows everything] WITH STREAMING Time ----------------------------------------------> Server [header] [reviews....] [deals.......] Browser [shows header][shows reviews][shows deals]
The total work stays the same. The visitor experience improves because the first content appears sooner.
Two Ways to Stream
Next.js offers two tools for streaming. One wraps a whole page. The other wraps individual sections.
Page-Level Streaming With loading.js
Add a loading.js file beside page.js. Next.js shows that file immediately and swaps in the page once its data is ready.
// app/products/loading.js
export default function Loading() {
return <p>Loading products...</p>;
}This approach works well when the whole page depends on one slow request.
Section-Level Streaming With Suspense
Suspense is a built-in React component. It wraps a slow section and shows a fallback until that section is ready. Everything outside the wrapper appears instantly.
import { Suspense } from 'react';
import Reviews from './Reviews';
import Deals from './Deals';
export default function ProductPage() {
return (
<div>
<h1>Wireless Headphones</h1>
<p>Comfortable fit and long battery life.</p>
<Suspense fallback={<p>Loading reviews...</p>}>
<Reviews />
</Suspense>
<Suspense fallback={<p>Loading deals...</p>}>
<Deals />
</Suspense>
</div>
);
}The heading and description appear at once. The review and deal sections arrive independently, each one the moment its data is ready.
The Slow Component
A slow section is an async Server Component. It awaits its own data inside the component body.
// Reviews.js
export default async function Reviews() {
const res = await fetch('https://api.example.com/reviews');
const reviews = await res.json();
return (
<ul>
{reviews.map((r) => (
<li key={r.id}>{r.text}</li>
))}
</ul>
);
}The parent page does not wait for this component. React pauses only the wrapped section and continues rendering the rest.
Sketch of a Streamed Page
+--------------------------------------+ | Header and title (instant) | +--------------------------------------+ | Description text (instant) | +--------------------------------------+ | [ Loading reviews... ] -> Reviews | +--------------------------------------+ | [ Loading deals... ] -> Deals | +--------------------------------------+
Each gray placeholder becomes real content as the server finishes the matching work.
Designing Good Fallbacks
A fallback should match the size of the final content. A tiny spinner followed by a large block makes the page jump and frustrates readers. A skeleton placeholder with similar height keeps the layout steady.
Skeleton Example
function ReviewsSkeleton() {
return (
<div>
<div>Loading review one</div>
<div>Loading review two</div>
<div>Loading review three</div>
</div>
);
}Parallel Data Loading
Separate Suspense boundaries let sections fetch data at the same time. The reviews request and the deals request start together, and neither one blocks the other. Sequential awaits inside a single component remove this benefit, so split independent data into separate components.
Streaming and Search Engines
Streamed content still reaches search engine crawlers because the server sends complete HTML as the stream finishes. Keep important text in server-rendered sections rather than in fallbacks.
When to Skip Streaming
- Pages that load all data in milliseconds gain little from extra boundaries.
- Fully static pages already arrive fast from a CDN.
- Tiny components do not need individual wrappers.
Common Mistakes
Wrapping Everything
Too many boundaries create a flickering page with placeholders everywhere. Wrap only sections that carry noticeable delay.
Awaiting Data in the Parent
If the page component awaits slow data before it returns anything, no streaming happens. Move the await into a child component inside Suspense.
Quick Summary
Streaming sends ready content first and slow content later. Use loading.js for whole pages and Suspense for individual sections. Visitors see progress sooner, and your page feels faster even when the data sources stay the same.
