Suspense SSR — Using React Suspense with Server-Side Rendering
In this tutorial, you will learn about Suspense SSR. We cover key concepts, practical examples, and best practices to help you master this topic.
Suspense SSR enables streaming server rendering by wrapping data-fetching components in Suspense boundaries, allowing progressive HTML delivery and faster page loads.
What You'll Learn
By the end of this tutorial, you will understand how React Suspense works with SSR, how to wrap components in Suspense boundaries for streaming, how Suspense integrates with frameworks like Next.js, and best practices for using Suspense in SSR applications.
Why It Matters
Suspense is the key that unlocks streaming SSR. Without Suspense, the server must render everything before sending anything. With Suspense, the server streams the HTML shell immediately, and each Suspense boundary streams its content independently as data becomes available.
Real-World Use
A news homepage uses Suspense boundaries for each section: breaking news (fast API), featured articles (database query), weather widget (external API), and sports scores (slow feed). The page shell appears instantly. Breaking news streams in 200ms. Weather shows in 800ms. Sports scores arrive after 3 seconds. Users read content while other sections still load.
Suspense SSR Streaming Order
┌──────────────────────────────────────────────────────────┐
│ Suspense SSR — Streaming Order │
├──────────────────────────────────────────────────────────┤
│ │
│ Time 0ms: HTML Shell │
│ ┌──────────────────────────────────────┐ │
│ │ Header, Navigation, CSS │ │
│ │ Fallback: Loading... │ │
│ │ Fallback: Loading... │ │
│ │ Footer │ │
│ └──────────────────────────────────────┘ │
│ │
│ Time 200ms: Suspense Boundary A (fast API) │
│ ┌──────────────────────────────────────┐ │
│ │ Breaking News Section │ │
│ │ (replaces fallback) │ │
│ └──────────────────────────────────────┘ │
│ │
│ Time 800ms: Suspense Boundary B (database) │
│ ┌──────────────────────────────────────┐ │
│ │ Featured Articles │ │
│ │ (replaces fallback) │ │
│ └──────────────────────────────────────┘ │
│ │
│ Time 3s: Suspense Boundary C (slow API) │
│ ┌──────────────────────────────────────┐ │
│ │ Sports Scores │ │
│ │ (replaces fallback) │ │
│ └──────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Think of Suspense SSR like a restaurant serving a multi-course meal. Instead of waiting for all courses to be ready before serving anything, the waiter brings each course as it is ready. The appetizer (shell) comes immediately. The main course (fast content) follows shortly. The dessert (slow content) arrives last. You start eating while the kitchen finishes the remaining courses.
Suspense with Data Fetching
import { Suspense } from 'react';
// Data-fetching component (uses a framework like SWR or React Query)
async function BreakingNews() {
const news = await fetchNews(); // Suspends while fetching
return (
<div className="breaking-news">
<h2>Breaking News</h2>
{news.map(item => (
<article key={item.id}>
<h3>{item.title}</h3>
<p>{item.summary}</p>
</article>
))}
</div>
);
}
// Simplified fallback
function NewsSkeleton() {
return (
<div className="skeleton">
<div className="skeleton-title" />
<div className="skeleton-text" />
<div className="skeleton-text" />
</div>
);
}
// Parent component with Suspense boundaries
function HomePage() {
return (
<div>
<header>
<h1>News Portal</h1>
<nav>...</nav>
</header>
<main>
{/* Each Suspense boundary streams independently */}
<Suspense fallback={<NewsSkeleton />}>
<BreakingNews />
</Suspense>
<Suspense fallback={<ArticleSkeleton />}>
<FeaturedArticles />
</Suspense>
<Suspense fallback={<WeatherSkeleton />}>
<WeatherWidget />
</Suspense>
</main>
<footer>...</footer>
</div>
);
}
Suspense in Next.js App Router
// app/page.js — Next.js App Router with Suspense
import { Suspense } from 'react';
// Server Components with async data fetching
async function ProductGrid() {
const products = await fetch('https://api.example.com/products', {
cache: 'no-store'
}).then(r => r.json());
// This component suspends while data fetches
// On the server, it streams when ready
return (
<div className="grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
function ProductGridSkeleton() {
return (
<div className="grid">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="skeleton-card">
<div className="skeleton-image" />
<div className="skeleton-title" />
<div className="skeleton-price" />
</div>
))}
</div>
);
}
export default function HomePage() {
return (
<div>
<h1>Product Catalog</h1>
<Suspense fallback={<ProductGridSkeleton />}>
<ProductGrid />
</Suspense>
</div>
);
}
// loading.js — Next.js automatically wraps page in Suspense
// app/products/loading.js
export default function Loading() {
return (
<div className="page-skeleton">
<div className="skeleton-header" />
<div className="skeleton-content" />
</div>
);
}
Common Mistakes
- Nesting Suspense boundaries incorrectly. Suspense boundaries should wrap individual data-fetching components, not large sections. Overly large boundaries delay showing any content.
- Not providing meaningful fallbacks. A blank div or text spinner is poor UX. Use skeleton screens that match the content layout for a smooth experience.
- Too many Suspense boundaries. Each boundary adds overhead. Group related data fetches in the same boundary. A page should have 3-5 Suspense boundaries at most.
- Using Suspense without a data framework. React Suspense needs a data fetching framework (SWR, React Query, Relay) that integrates with Suspense. Plain fetch does not suspend automatically.
- Forgetting about the loading state. Suspense only handles the initial loading. For subsequent data refetches, use useTransition or separate loading states.
Practice Questions
- How does Suspense enable streaming SSR?
- What is a Suspense boundary and how do you create one?
- Why should you use skeleton screens as fallbacks?
- How does the Next.js App Router integrate with Suspense?
- What is the difference between Suspense boundaries and loading.js?
Challenge: Build a news homepage with 3 Suspense boundaries: breaking news (1s delay), featured articles (2s delay), and weather widget (3s delay). Each boundary should have an appropriate skeleton fallback. Measure the time each section appears. Compare with a non-Suspense version.
FAQ
Mini Project
Build a dashboard page with 4 Suspense boundaries: user stats (fast cache), activity feed (API), notifications (slow), and recommendations (complex query). Each boundary has a skeleton fallback. Implement error boundaries for each section. Measure and compare streaming vs non-streaming page load.
What's Next
You understand Suspense SSR. Now explore Progressive Hydration to hydrate components incrementally for better interactivity.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro