Streaming SSR permite enviar HTML em pedaços, sem esperar a página inteira renderizar.
O Problema do SSR Tradicional
No Next.js 12 (Pages Router):
- Servidor busca todos os dados
- Renderiza toda a página
- Envia HTML completo
Se uma query demora 5s, o usuário espera 5s para ver qualquer coisa.
Streaming: Enviando Progressivamente
Com Streaming SSR:
- Servidor envia shell HTML imediatamente (< 50ms)
- Renderiza componentes conforme dados chegam
- "Injeta" HTML via
<script>tags
Usuário vê conteúdo estático instantaneamente.
Next.js App Router: Streaming Nativo
No Next.js 13+, streaming é default:
// app/page.jsx
import { Suspense } from 'react';
async function UserPosts() {
const posts = await fetch('https://api.site.com/posts').then(r => r.json());
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
export default function Page() {
return (
<div>
<h1>Meu Blog</h1> {/* Envia IMEDIATAMENTE */}
<Suspense fallback={<p>Carregando posts...</p>}>
<UserPosts /> {/* Stream quando dados chegarem */}
</Suspense>
</div>
);
}
Fluxo:
- HTML inicial:
<h1>Meu Blog</h1> + <p>Carregando posts...</p> - Fetch completa → Stream HTML dos posts
- React "substitui" o fallback
Como Funciona Internamente?
O servidor envia HTML "incompleto" + scripts:
<!DOCTYPE html>
<html>
<body>
<div id="root">
<h1>Meu Blog</h1>
<template id="B:0"></template>
<p>Carregando posts...</p>
</div>
<script>
// React hydration progressivo
</script>
</body>
</html>
<!-- 2 segundos depois... -->
<script>
$RC("B:0", "<ul><li>Post 1</li><li>Post 2</li></ul>");
</script>
React substitui o <template> com o HTML real.
Múltiplos Suspense Boundaries
Você pode ter vários Suspense:
export default function Dashboard() {
return (
<>
<h1>Dashboard</h1>
<Suspense fallback={<Skeleton />}>
<UserInfo /> {/* Fetch 1 */}
</Suspense>
<Suspense fallback={<Skeleton />}>
<RecentOrders /> {/* Fetch 2 */}
</Suspense>
<Suspense fallback={<Skeleton />}>
<Analytics /> {/* Fetch 3 */}
</Suspense>
</>
);
}
Cada seção stream independentemente. O que chegar primeiro, renderiza primeiro.
loading.js: Suspense Automático
Next.js cria Suspense automaticamente com loading.js:
app/
dashboard/
page.jsx
loading.jsx ← Fallback automático
// app/dashboard/loading.jsx
export default function Loading() {
return <Skeleton />;
}
O page.jsx é automaticamente wrapped em <Suspense fallback={<Loading />}>.
Performance: Números Reais
Site de e-commerce (6 queries SQL):
Sem Streaming (SSR tradicional)
- TTFB: 2.3s
- FCP: 2.5s
Com Streaming
- TTFB: 80ms (shell HTML)
- FCP: 120ms (conteúdo estático)
- Produtos: 800ms (stream)
- Reviews: 1.2s (stream)
28x melhor TTFB. Usuário vê conteúdo 20x mais rápido.
SEO: Google Vê HTML Completo?
Sim. Googlebot espera a página terminar de stream antes de indexar.
Mas Core Web Vitals melhoram porque FCP é mais rápido.
Quando NÃO Usar Streaming?
❌ SEO crítico + dados essenciais (use await no root)
❌ Paywall/Auth que bloqueia conteúdo
❌ Apps sem dados assíncronos
Remix: Streaming Defer
Remix tem API explícita:
// routes/dashboard.tsx
import { defer } from '@remix-run/node';
import { Await, useLoaderData } from '@remix-run/react';
export async function loader() {
return defer({
critical: await getCriticalData(), // Bloqueia
slow: getSlowData(), // Stream
});
}
export default function Dashboard() {
const { critical, slow } = useLoaderData();
return (
<div>
<h1>{critical.title}</h1>
<Suspense fallback={<Skeleton />}>
<Await resolve={slow}>
{(data) => <SlowComponent data={data} />}
</Await>
</Suspense>
</div>
);
}
Conclusão: Streaming SSR é a evolução natural do SSR. Melhora performance drasticamente sem trade-offs de SEO.