Felipe Moacir

Streaming SSR: HTML em Chunks

SSRReactPerformance
Streaming SSR: HTML em Chunks

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):

  1. Servidor busca todos os dados
  2. Renderiza toda a página
  3. Envia HTML completo

Se uma query demora 5s, o usuário espera 5s para ver qualquer coisa.

Streaming: Enviando Progressivamente

Com Streaming SSR:

  1. Servidor envia shell HTML imediatamente (< 50ms)
  2. Renderiza componentes conforme dados chegam
  3. "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:

  1. HTML inicial: <h1>Meu Blog</h1> + <p>Carregando posts...</p>
  2. Fetch completa → Stream HTML dos posts
  3. 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.