Streaming e Suspense no React
1. Introdução ao Streaming e Suspense no React
O React moderno introduziu duas poderosas abstrações que transformaram a forma como construímos aplicações web: Streaming e Suspense. Streaming permite que o servidor envie partes do HTML progressivamente ao cliente, enquanto o Suspense oferece um mecanismo declarativo para lidar com estados de carregamento em componentes assíncronos.
No modelo tradicional, o servidor precisa processar toda a árvore de componentes antes de enviar qualquer HTML ao navegador. Com Streaming + Suspense, o React pode começar a enviar conteúdo imediatamente, reduzindo drasticamente o TTFB (Time to First Byte) e permitindo que o usuário interaja com partes da página enquanto outras ainda estão sendo processadas.
2. Suspense para Carregamento de Dados no Cliente
O uso mais básico do Suspense no cliente é com React.lazy para code splitting:
import React, { Suspense } from 'react';
const DashboardComponent = React.lazy(() => import('./Dashboard'));
function App() {
return (
<Suspense fallback={<div>Carregando dashboard...</div>}>
<DashboardComponent />
</Suspense>
);
}
Para integração com bibliotecas de data fetching como React Query:
import { useSuspenseQuery } from '@tanstack/react-query';
import { Suspense } from 'react';
function UserProfile() {
const { data } = useSuspenseQuery({
queryKey: ['user', 1],
queryFn: () => fetch('/api/user/1').then(res => res.json())
});
return <div>{data.name}</div>;
}
function Page() {
return (
<Suspense fallback={<UserSkeleton />}>
<UserProfile />
</Suspense>
);
}
Para transições suaves, use startTransition:
import { startTransition } from 'react';
function handleSearch(query) {
startTransition(() => {
setSearchQuery(query);
});
}
3. Streaming no Servidor com React 18 e Node.js
No servidor Node.js, o renderToPipeableStream permite enviar HTML progressivamente:
import express from 'express';
import { renderToPipeableStream } from 'react-dom/server';
import App from './App';
const app = express();
app.get('/', (req, res) => {
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
res.setHeader('Content-Type', 'text/html');
res.write('<html><head><title>Streaming App</title></head><body><div id="root">');
pipe(res);
res.write('</div></body></html>');
},
onError(err) {
console.error(err);
res.statusCode = 500;
res.end('Erro interno');
}
});
});
Diferentemente do renderToString (que bloqueia até processar tudo), o renderToPipeableStream começa a enviar o "shell" da página imediatamente, enquanto componentes envolvidos em Suspense são enviados conforme ficam prontos.
4. Suspense no Servidor: Componentes Assíncronos e Data Fetching
No servidor, o Suspense permite pausar a renderização de partes específicas da árvore enquanto dados são buscados:
import { Suspense } from 'react';
async function fetchComments(postId) {
const res = await fetch(`https://api.example.com/posts/${postId}/comments`);
return res.json();
}
function Comments({ postId }) {
const comments = use(fetchComments(postId));
return (
<ul>
{comments.map(comment => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
function BlogPost({ postId }) {
return (
<article>
<h1>Post Title</h1>
<p>Conteúdo principal...</p>
<Suspense fallback={<div>Carregando comentários...</div>}>
<Comments postId={postId} />
</Suspense>
</article>
);
}
Estratégias de fallback no servidor podem incluir conteúdo estático ou placeholders customizados que mantêm o layout estável.
5. Construindo uma Aplicação com Streaming e Suspense (Exemplo Prático)
Aqui está um exemplo completo com Express.js:
import express from 'express';
import { renderToPipeableStream } from 'react-dom/server';
import React, { Suspense } from 'react';
// Componente que simula busca lenta de dados
function SlowComponent({ delay }) {
return new Promise(resolve => {
setTimeout(() => {
resolve(<div>Dados carregados após {delay}ms</div>);
}, delay);
});
}
function HomePage() {
return (
<html>
<head><title>Streaming App</title></head>
<body>
<h1>Minha Página Streamada</h1>
<Suspense fallback={<div>Carregando parte 1...</div>}>
<SlowComponent delay={2000} />
</Suspense>
<Suspense fallback={<div>Carregando parte 2...</div>}>
<SlowComponent delay={4000} />
</Suspense>
<footer>Footer - carregado imediatamente</footer>
</body>
</html>
);
}
const server = express();
server.get('/', (req, res) => {
const { pipe } = renderToPipeableStream(<HomePage />, {
onShellReady() {
res.setHeader('Content-Type', 'text/html');
pipe(res);
},
onError(err) {
console.error(err);
res.statusCode = 500;
res.end('Erro');
}
});
});
server.listen(3000, () => {
console.log('Servidor rodando em http://localhost:3000');
});
O navegador receberá primeiro o shell da página (título, footer), depois cada seção Suspense conforme fica pronta.
6. Boas Práticas e Considerações de Performance
Quando usar Streaming:
- Páginas com conteúdo dinâmico de diferentes fontes
- Aplicações que precisam de First Paint rápido
- Conteúdo acima da dobra que pode ser servido imediatamente
Cuidados importantes:
- Metadados e SEO: use tags no shell inicial
- Cache: configure caching adequado para partes estáticas
- Debugging: erros em stream são mais complexos de rastrear
// Estratégia de cache com streaming
app.get('/', (req, res) => {
res.setHeader('Cache-Control', 'public, max-age=300');
// Streaming continua funcionando com cache
});
7. Comparação com Abordagens Tradicionais e Futuro
Métricas comparativas:
| Métrica | renderToString | renderToPipeableStream |
|---|---|---|
| TTFB | Alto (processa tudo) | Baixo (envia shell) |
| First Paint | Após processamento total | Imediato |
| Interação | Aguarda hidratação total | Progressiva |
O Streaming + Suspense é a base para os Server Components no React 18+, que levam essa abordagem ao próximo nível. Frameworks como Next.js (com App Router) e Remix já estão adotando essas APIs nativamente.
O futuro aponta para uma web onde o servidor envia componentes interativos sob demanda, combinando o melhor da renderização server-side com a riqueza de interações client-side.
Referências
- React Documentation: Suspense — Documentação oficial do React sobre o componente Suspense, incluindo uso no cliente e servidor
- React 18: renderToPipeableStream — API oficial para streaming no servidor com Node.js
- The New React Architecture: Streaming and Suspense — Artigo da Vercel explicando como React 18 melhora performance com streaming
- React Query + Suspense Integration — Guia oficial do TanStack Query sobre uso com Suspense
- Next.js: Streaming and Suspense — Documentação do Next.js sobre implementação prática de streaming e Suspense
- Remix: Streaming — Guia do Remix sobre como implementar streaming de respostas no servidor
- React 18: Concurrent Features Deep Dive — Palestra técnica do time do React explicando os fundamentos do streaming e concorrência