Capítulo 83 de 456

Streaming

Core Idea

Streaming envia partes da resposta HTML assim que ficam prontas (chunked transfer encoding), em vez de esperar a página inteira renderizar — o navegador começa a pintar enquanto o servidor ainda gera o resto, alinhado a boundaries <Suspense>.

Key Concepts

  • HTML stream: chunks progressivos de HTML; partes estáticas (layouts, fallbacks) chegam primeiro; quando um <Suspense> resolve, React envia o HTML pronto + <script> que faz o swap no DOM instantaneamente, sem esperar o bundle JS carregar.
  • Component payload: representação serializada da árvore de componentes usada para hidratação; no load inicial vai embutido no HTML stream; em navegação client-side só o payload é buscado (header rsc: 1), sem HTML.
  • Static shell: tudo que renderiza antes de qualquer trabalho assíncrono resolver (layouts, navegação, fallbacks de Suspense); enviado imediatamente. Com Cache Components é prerenderizado no build e servido do edge.
  • loading.js: envolve automaticamente page.js num <Suspense> boundary usando o arquivo como fallback; escopo é a página inteira; é prefetched como fallback instantâneo na navegação.
  • <Suspense> granular: permite controlar exatamente quais seções streamam independentemente; boundaries irmãs resolvem em paralelo sem se bloquear; boundaries aninhadas criam revelação progressiva (nested).
  • Push dynamic access down: adiar acesso a params, searchParams, cookies(), headers() e fetches até o componente que realmente precisa — await no topo do layout/page torna tudo abaixo dinâmico e não-prerenderizável.
  • use(): hook React para ler uma Promise passada como prop; só o componente que chama use() precisa de <Suspense> ao redor.
  • HTTP contract: uma vez que o streaming começa, status code e headers já foram enviados e não podem mudar — notFound() mid-stream vira <meta name="robots" content="noindex"> em vez de 404 real; redirect() mid-stream vira redirect client-side em vez de header HTTP.
  • htmlLimitedBots: bots limitados a HTML (sem execução de JS) recebem metadata bloqueante — Next.js espera generateMetadata resolver antes de streamar; browsers completos recebem streaming metadata.

Code Examples

import { Suspense } from 'react'
import { Revenue } from './revenue'
import { RecentOrders } from './recent-orders'

export default function Dashboard() {
  return (
    <div>
      <Suspense fallback={<p>Loading revenue...</p>}>
        <Revenue />
      </Suspense>
      <Suspense fallback={<p>Loading orders...</p>}>
        <RecentOrders />
      </Suspense>
    </div>
  )
}
  • O que demonstra: boundaries irmãs streamam de forma independente e em paralelo, na ordem em que cada uma resolve.
const cookieStore = cookies() // Start the work, but don't await
return (
  <div>
    <Nav>
      <Suspense fallback={<p>Loading user...</p>}>
        <UserMenu cookiePromise={cookieStore} />
      </Suspense>
    </Nav>
    {children}
  </div>
)
  • O que demonstra: <Nav> e {children} entram no static shell porque nada no layout dá await; só <UserMenu> suspende ao resolver a Promise de cookies.
const { slug } = await params
const exists = await checkSlugExists(slug) // Fast existence check
if (!exists) notFound() // Real 404, before any Suspense boundary

return (
  <Suspense fallback={<p>Loading post...</p>}>
    <PostContent slug={slug} />
  </Suspense>
)
  • O que demonstra: para obter um 404 HTTP real (não apenas noindex), notFound() deve rodar antes de qualquer await/boundary que já tenha iniciado o streaming.
module.exports = {
  async headers() {
    return [{ source: '/:path*{/}?', headers: [{ key: 'X-Accel-Buffering', value: 'no' }] }]
  },
}
  • O que demonstra: desabilitar buffering do nginx, um dos vários pontos da infraestrutura (proxy, CDN, serverless, compressão, cliente) que pode anular o benefício do streaming.

Reference Tables

loading.js<Suspense>
EscopoPágina inteiraQualquer componente
SetupColocar um arquivoEnvolver componentes explicitamente
NavegaçãoPrefetched como fallback instantâneoNão prefetched por padrão
Melhor paraPáginas onde nada renderiza sem dadoMaioria das páginas, controle granular
PlataformaSuporte a streaming
Node.js serverSim
Docker containerSim
Static exportNão
AdaptersDepende da plataforma

Anti-patterns

  • await cookies()/headers()/params no topo do layout ou page: bloqueia tudo abaixo daquele ponto de ser prerenderizado como parte do static shell.
  • Colocar elemento LCP (hero image, heading principal) dentro de um <Suspense>: ele só pinta quando a boundary resolve e o script de swap executa; mantenha elementos LCP fora/acima de boundaries.
  • Fallback de skeleton com dimensões diferentes do conteúdo final: causa Cumulative Layout Shift (CLS) quando o swap acontece; desenhe skeletons com as mesmas dimensões do conteúdo real.
  • Confiar em curl puro para verificar streaming: curl tem buffering próprio e depende de newlines para exibir linha a linha; use um script que leia response.body como stream, ou o Network tab do DevTools.
  • Assumir que toda infraestrutura passa streaming adiante: reverse proxies (nginx), CDNs, serverless (ex. AWS Lambda sem response streaming mode habilitado), compressão (gzip/brotli) e até o próprio cliente (Safari bufffera até 1024 bytes) podem bufferizar a resposta inteira.

Key Takeaways

  1. O gatilho de streaming é o código do dev (trabalho assíncrono, output não-determinístico, dado de runtime); o framework sobe a árvore procurando a <Suspense> mais próxima como fallback.
  2. As duas decisões-chave para maximizar streaming: o que cachear (use cache, cresce o static shell) e onde colocar as boundaries de <Suspense> (empurradas para perto do acesso dinâmico).
  3. Status code e headers HTTP são fixados assim que o streaming começa — não é possível "voltar atrás" para mudar pra 404/redirect real depois disso.
  4. Streaming melhora TTFB/FCP (shell chega rápido), habilita hidratação seletiva (melhora INP) e descobre recursos (CSS/JS/fonts) cedo, mas exige cuidado com LCP e CLS.
  5. Static export não suporta streaming — é incompatível com essa arquitetura.

Connects To

  • Rendering Philosophy (ch075): streaming é o requisito de infraestrutura central decorrente da filosofia de fronteira em nível de componente.
  • Self-Hosting (ch078): instruções operacionais de configuração de proxy/nginx para não bufferizar.
  • Public pages (ch073): exemplo prático de streaming combinado com cache components (PPR).
  • SPAs (ch081): mesmo padrão use() + Promise-as-prop usado aqui para streaming de dado ao cliente.