Performance has always been the backbone of great web applications. With Next.js 15, the framework doubles down on its promise by giving developers first-class tools for Streaming and React Suspense—two technologies that redefine how we deliver content to users.
In this post, we’ll break down what these features mean, why they matter, and how you can use them to build blazing-fast, resilient, and user-friendly apps.
What is Streaming in Next.js?
Traditionally, SSR waits until the entire page is ready before sending HTML.
❌ Bad for UX (slow loading)
❌ Blank screen until everything is fetched
Streaming changes this:
✅ Next.js sends partial HTML chunks as soon as they’re ready
✅ Users see content progressively
✅ Ideal for dashboards, news feeds, social apps, and e-commerce
📖 Official Next.js Streaming Docs
Suspense: The Secret Sauce
React Suspense lets you wrap components that take time (e.g., data fetching). Instead of blocking, you show a placeholder UI.
// app/page.tsx
import { Suspense } from "react";
function LoadingPosts() {
return <p>Loading posts...</p>;
}
async function Posts() {
const res = await fetch("https://api.example.com/posts", {
cache: "no-store",
});
const posts: { id: number; title: string }[] = await res.json();
return (
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
);
}
export default function Page() {
return (
<div>
<h1>Blog</h1>
<Suspense fallback={<LoadingPosts />}>
<Posts />
</Suspense>
</div>
);
}
💡 The page renders instantly with <LoadingPosts />
→ then streams posts once ready.
Streaming Layouts with App Router
In the App Router, layouts can stream while children load.
// app/layout.tsx
import { Suspense } from "react";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<header>⚡ My Next.js App</header>
<Suspense fallback={<p>Loading page...</p>}>
{children}
</Suspense>
<footer>© 2025</footer>
</body>
</html>
);
}
✅ Header & Footer load instantly
✅ Main content streams in dynamically
Parallel Data Fetching
No need to wait for all data sources before rendering — fetch in parallel with Suspense.
// app/dashboard/page.tsx
import { Suspense } from "react";
import UserInfo from "./UserInfo";
import Analytics from "./Analytics";
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading user info...</p>}>
<UserInfo />
</Suspense>
<Suspense fallback={<p>Loading analytics...</p>}>
<Analytics />
</Suspense>
</div>
);
}
✅ Users see parts of the dashboard immediately → no waiting for everything.
Route-Level Loading States with loading.tsx
Next.js automatically supports route-level loading states.
// app/blog/loading.tsx
export default function Loading() {
return <p>Fetching blog posts...</p>;
}
Whenever /blog
is loading, this UI shows instantly, then streams real data.
Common Mistakes Developers Make
❌ Wrapping the entire app in one
<Suspense>
→ defeats the purpose❌ Fetching inside client components → breaks streaming (use server components)
❌ Forgetting fallbacks → leads to blank screens
❌ Over-streaming → too many small chunks = performance overhead
Best Practices for Streaming in Next.js 15
Break UI into smaller Suspense boundaries for fine-grained control
Always provide meaningful fallbacks (skeletons, previews, loaders)
Use server components for data fetching → client components = interactivity
Combine with ISR + Edge Functions for max performance
Monitor streaming performance with Vercel Analytics / Lighthouse
Conclusion
Streaming with Suspense in Next.js 15 is a superpower. It enables developers to:
Load faster (progressive rendering)
Scale better (parallel fetching)
Improve UX (no blank screens)
© 2025 Kristiyan Velkov . All rights reserved.