What is Streaming SSR and how does React 18+ improve it?
Assesses fundamental understanding of React conventions, runtime behavior, and memory/performance considerations.
Hiring managers look for precision, avoidance of ambiguous jargon, and ability to explain trade-offs under real production conditions.
Streaming SSR sends HTML to the browser in chunks as it's generated, rather than waiting for the entire page. React 18+ dramatically improves this with Suspense integration.
#### Traditional SSR (Pre-React 18)
Server: Wait for ALL data → Generate ALL HTML → Send to client
Client: Receive HTML → Download ALL JS → Hydrate ALL components
Problem: Slow components block entire page!
#### Streaming SSR (React 18+)
Server: Send HTML as it's ready, wrap slow parts in <Suspense>
Client: Render immediately, hydrate progressively
Benefit: User sees content faster!
#### Basic Example
import { Suspense } from 'react';
export default function Page() {
return (
<html>
<body>
{/* Sent immediately */}
<header>
<h1>My App</h1>
</header>
{/* Sent immediately with fallback */}
<Suspense fallback={<div>Loading comments...</div>}>
<Comments /> {/* Streamed when ready */}
</Suspense>
{/* Also streamed separately */}
<Suspense fallback={<div>Loading recommendations...</div>}>
<Recommendations /> {/* Streamed when ready */}
</Suspense>
<footer>© 2026</footer>
</body>
</html>
);
}
#### Server Component with Data Fetching
// This is a Server Component (async!)
async function Comments() {
const comments = await db.comments.findMany();
return (
<ul>
{comments.map(comment => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
#### How It Works
- Server starts sending HTML immediately
- When it hits
<Suspense>, it sends the fallback - Continues streaming rest of the page
- When data is ready, sends the actual component
- Client replaces fallback with real content
- Hydration happens independently per component
#### Selective Hydration
function App() {
return (
<div>
<header>Header</header> {/* Hydrates first */}
<Suspense fallback={<Spinner />}>
<HeavyComponent /> {/* Hydrates when user interacts */}
</Suspense>
<Suspense fallback={<Spinner />}>
<Comments /> {/* Hydrates independently */}
</Suspense>
</div>
);
}
#### Benefits
- Faster TTFB (Time to First Byte): User sees content sooner
- Better UX: Progressive loading instead of blank screen
- Prioritized hydration: Interactive elements hydrate first
- Resilient: Slow components don't block fast ones
#### Next.js App Router Example
// app/page.tsx
import { Suspense } from 'react';
import ProductList from './ProductList';
import Reviews from './Reviews';
export default function ProductPage() {
return (
<div>
<h1>Product Page</h1>
{/* Streams product list first */}
<Suspense fallback={<ProductSkeleton />}>
<ProductList />
</Suspense>
{/* Reviews stream separately */}
<Suspense fallback={<ReviewSkeleton />}>
<Reviews />
</Suspense>
</div>
);
}
// These are async Server Components
async function ProductList() {
const products = await fetchProducts(); // Doesn't block Reviews
return <div>{/* render products */}</div>;
}
async function Reviews() {
const reviews = await fetchReviews(); // Doesn't block ProductList
return <div>{/* render reviews */}</div>;
}
#### Key Requirements
- Use React 18+ with
createRootandhydrateRoot - Wrap slow components in
<Suspense> - Use frameworks supporting streaming (Next.js, Remix, etc.)
- Server must support streaming responses
Candidate Response Strategy & Interview Tips
- Start with a concise one-sentence summary: Deliver a direct, confident answer first before expanding into nuances.
- Demonstrate real-world trade-offs: Discuss where this approach excels and when you would avoid it in production systems.
- Discuss complexity & edge cases: Proactively explain time/space complexity or boundary conditions (null values, scale limits).
- Prepare for interviewer follow-ups: Technical hiring panels frequently probe deeper into concurrency, backward compatibility, or alternative libraries.