React 19 Server Components: Guia Pratico Para Dominar a Nova Era do React
Ola HaWkers, o React 19 trouxe uma mudanca fundamental na forma como construimos aplicacoes web. Os Server Components representam a maior evolucao do React desde a introducao dos Hooks, e entender esse conceito e essencial para qualquer desenvolvedor frontend em 2025.
Voce ja se perguntou por que suas aplicacoes React enviam tanto JavaScript para o navegador? Os Server Components chegaram para resolver exatamente esse problema, permitindo que componentes sejam renderizados no servidor sem enviar codigo desnecessario ao cliente.
O Que Sao Server Components
Server Components sao componentes React que executam exclusivamente no servidor. Diferente dos componentes tradicionais que rodam no navegador, eles nunca sao enviados para o cliente, resultando em bundles menores e melhor performance.

Beneficios Principais
- Zero JavaScript no cliente: Componentes de servidor nao adicionam ao bundle
- Acesso direto a recursos: Banco de dados, sistema de arquivos, APIs internas
- Streaming automatico: Conteudo enviado progressivamente ao usuario
- SEO otimizado: HTML completo disponivel para crawlers
Client Components vs Server Components
A distincao entre esses dois tipos de componentes e fundamental para arquitetar aplicacoes React modernas.
Server Components (Padrao no React 19)
// app/produtos/page.tsx
// Este e um Server Component por padrao
import { db } from '@/lib/database';
interface Produto {
id: number;
nome: string;
preco: number;
descricao: string;
}
async function buscarProdutos(): Promise<Produto[]> {
// Acesso direto ao banco - impossivel em Client Components
const produtos = await db.query('SELECT * FROM produtos ORDER BY criado_em DESC');
return produtos;
}
export default async function PaginaProdutos() {
const produtos = await buscarProdutos();
return (
<main className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-6">Nossos Produtos</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{produtos.map((produto) => (
<article key={produto.id} className="border rounded-lg p-4">
<h2 className="text-xl font-semibold">{produto.nome}</h2>
<p className="text-gray-600 mt-2">{produto.descricao}</p>
<p className="text-2xl font-bold mt-4 text-green-600">
R$ {produto.preco.toFixed(2)}
</p>
</article>
))}
</div>
</main>
);
}Client Components (Interatividade)
// components/BotaoCarrinho.tsx
'use client'; // Diretiva obrigatoria para Client Components
import { useState } from 'react';
interface BotaoCarrinhoProps {
produtoId: number;
nome: string;
preco: number;
}
export function BotaoCarrinho({ produtoId, nome, preco }: BotaoCarrinhoProps) {
const [quantidade, setQuantidade] = useState(1);
const [adicionado, setAdicionado] = useState(false);
const handleAdicionar = async () => {
try {
await fetch('/api/carrinho', {
method: 'POST',
body: JSON.stringify({ produtoId, quantidade }),
headers: { 'Content-Type': 'application/json' }
});
setAdicionado(true);
setTimeout(() => setAdicionado(false), 2000);
} catch (error) {
console.error('Erro ao adicionar ao carrinho:', error);
}
};
return (
<div className="flex items-center gap-4 mt-4">
<div className="flex items-center border rounded">
<button
onClick={() => setQuantidade(Math.max(1, quantidade - 1))}
className="px-3 py-1 hover:bg-gray-100"
>
-
</button>
<span className="px-4">{quantidade}</span>
<button
onClick={() => setQuantidade(quantidade + 1)}
className="px-3 py-1 hover:bg-gray-100"
>
+
</button>
</div>
<button
onClick={handleAdicionar}
disabled={adicionado}
className={`px-6 py-2 rounded font-medium transition-colors ${
adicionado
? 'bg-green-500 text-white'
: 'bg-blue-600 text-white hover:bg-blue-700'
}`}
>
{adicionado ? 'Adicionado!' : 'Adicionar ao Carrinho'}
</button>
</div>
);
}
Composicao: Combinando Server e Client Components
A verdadeira magia acontece quando combinamos os dois tipos de componentes de forma estrategica.
// app/produtos/[id]/page.tsx
// Server Component - busca dados
import { db } from '@/lib/database';
import { BotaoCarrinho } from '@/components/BotaoCarrinho';
import { GaleriaImagens } from '@/components/GaleriaImagens';
import { AvaliacoesClientes } from '@/components/AvaliacoesClientes';
interface PageProps {
params: { id: string };
}
async function buscarProduto(id: string) {
const produto = await db.query(
'SELECT * FROM produtos WHERE id = ?',
[id]
);
return produto[0];
}
async function buscarAvaliacoes(produtoId: string) {
return await db.query(
'SELECT * FROM avaliacoes WHERE produto_id = ? ORDER BY data DESC LIMIT 10',
[produtoId]
);
}
export default async function PaginaProduto({ params }: PageProps) {
// Buscas paralelas no servidor
const [produto, avaliacoes] = await Promise.all([
buscarProduto(params.id),
buscarAvaliacoes(params.id)
]);
if (!produto) {
return <div>Produto nao encontrado</div>;
}
return (
<main className="container mx-auto p-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Client Component para interatividade da galeria */}
<GaleriaImagens imagens={produto.imagens} />
<div>
<h1 className="text-3xl font-bold">{produto.nome}</h1>
<p className="text-gray-600 mt-4">{produto.descricao}</p>
<div className="mt-6">
<span className="text-4xl font-bold text-green-600">
R$ {produto.preco.toFixed(2)}
</span>
</div>
{/* Client Component para adicionar ao carrinho */}
<BotaoCarrinho
produtoId={produto.id}
nome={produto.nome}
preco={produto.preco}
/>
</div>
</div>
{/* Server Component renderizado com dados do servidor */}
<section className="mt-12">
<h2 className="text-2xl font-bold mb-6">Avaliacoes dos Clientes</h2>
<AvaliacoesClientes avaliacoes={avaliacoes} />
</section>
</main>
);
}GaleriaImagens como Client Component
// components/GaleriaImagens.tsx
'use client';
import { useState } from 'react';
import Image from 'next/image';
interface GaleriaImagensProps {
imagens: string[];
}
export function GaleriaImagens({ imagens }: GaleriaImagensProps) {
const [imagemAtiva, setImagemAtiva] = useState(0);
return (
<div className="space-y-4">
<div className="relative aspect-square bg-gray-100 rounded-lg overflow-hidden">
<Image
src={imagens[imagemAtiva]}
alt="Imagem do produto"
fill
className="object-cover"
priority
/>
</div>
<div className="flex gap-2 overflow-x-auto">
{imagens.map((img, index) => (
<button
key={index}
onClick={() => setImagemAtiva(index)}
className={`relative w-20 h-20 rounded border-2 overflow-hidden flex-shrink-0 ${
index === imagemAtiva ? 'border-blue-500' : 'border-transparent'
}`}
>
<Image src={img} alt="" fill className="object-cover" />
</button>
))}
</div>
</div>
);
}
Streaming e Suspense
Uma das grandes vantagens dos Server Components e o streaming de conteudo com Suspense, permitindo que partes da pagina sejam exibidas enquanto outras ainda carregam.
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { EstatisticasGerais } from '@/components/EstatisticasGerais';
import { GraficoVendas } from '@/components/GraficoVendas';
import { TabelaPedidos } from '@/components/TabelaPedidos';
import { LoadingSpinner } from '@/components/LoadingSpinner';
export default function Dashboard() {
return (
<main className="p-6 space-y-6">
<h1 className="text-3xl font-bold">Dashboard</h1>
{/* Estatisticas carregam primeiro - rapidas */}
<Suspense fallback={<LoadingSpinner texto="Carregando estatisticas..." />}>
<EstatisticasGerais />
</Suspense>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Grafico pode demorar mais */}
<Suspense fallback={<LoadingSpinner texto="Carregando grafico..." />}>
<GraficoVendas />
</Suspense>
{/* Tabela independente do grafico */}
<Suspense fallback={<LoadingSpinner texto="Carregando pedidos..." />}>
<TabelaPedidos />
</Suspense>
</div>
</main>
);
}
// components/EstatisticasGerais.tsx
// Server Component - executa no servidor
import { db } from '@/lib/database';
async function buscarEstatisticas() {
const [vendas, clientes, pedidos] = await Promise.all([
db.query('SELECT SUM(valor) as total FROM vendas WHERE mes = CURRENT_MONTH'),
db.query('SELECT COUNT(*) as total FROM clientes WHERE ativo = true'),
db.query('SELECT COUNT(*) as total FROM pedidos WHERE status = "pendente"')
]);
return {
vendasMes: vendas[0].total,
clientesAtivos: clientes[0].total,
pedidosPendentes: pedidos[0].total
};
}
export async function EstatisticasGerais() {
const stats = await buscarEstatisticas();
return (
<div className="grid grid-cols-3 gap-4">
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-gray-500 text-sm">Vendas do Mes</h3>
<p className="text-3xl font-bold text-green-600">
R$ {stats.vendasMes.toLocaleString()}
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-gray-500 text-sm">Clientes Ativos</h3>
<p className="text-3xl font-bold text-blue-600">
{stats.clientesAtivos.toLocaleString()}
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-gray-500 text-sm">Pedidos Pendentes</h3>
<p className="text-3xl font-bold text-orange-600">
{stats.pedidosPendentes}
</p>
</div>
</div>
);
}
Server Actions: Mutacoes no Servidor
Server Actions permitem executar funcoes no servidor diretamente de componentes, simplificando drasticamente o tratamento de formularios.
// app/contato/page.tsx
import { enviarMensagem } from './actions';
export default function PaginaContato() {
return (
<main className="container mx-auto p-4 max-w-lg">
<h1 className="text-3xl font-bold mb-6">Entre em Contato</h1>
<form action={enviarMensagem} className="space-y-4">
<div>
<label htmlFor="nome" className="block text-sm font-medium mb-1">
Nome
</label>
<input
type="text"
id="nome"
name="nome"
required
className="w-full border rounded-lg px-4 py-2"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
type="email"
id="email"
name="email"
required
className="w-full border rounded-lg px-4 py-2"
/>
</div>
<div>
<label htmlFor="mensagem" className="block text-sm font-medium mb-1">
Mensagem
</label>
<textarea
id="mensagem"
name="mensagem"
rows={5}
required
className="w-full border rounded-lg px-4 py-2"
/>
</div>
<BotaoEnviar />
</form>
</main>
);
}
// Botao com estado de loading
'use client';
import { useFormStatus } from 'react-dom';
function BotaoEnviar() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50"
>
{pending ? 'Enviando...' : 'Enviar Mensagem'}
</button>
);
}// app/contato/actions.ts
'use server';
import { db } from '@/lib/database';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function enviarMensagem(formData: FormData) {
const nome = formData.get('nome') as string;
const email = formData.get('email') as string;
const mensagem = formData.get('mensagem') as string;
// Validacao no servidor
if (!nome || !email || !mensagem) {
throw new Error('Todos os campos sao obrigatorios');
}
if (!email.includes('@')) {
throw new Error('Email invalido');
}
// Salvar no banco
await db.query(
'INSERT INTO mensagens (nome, email, mensagem, criado_em) VALUES (?, ?, ?, NOW())',
[nome, email, mensagem]
);
// Enviar email de notificacao
await fetch(process.env.EMAIL_API_URL!, {
method: 'POST',
body: JSON.stringify({
to: process.env.ADMIN_EMAIL,
subject: `Nova mensagem de ${nome}`,
body: mensagem
})
});
// Revalidar cache e redirecionar
revalidatePath('/contato');
redirect('/contato/sucesso');
}
Padroes de Cache e Revalidacao
Entender o sistema de cache do React 19 e Next.js e crucial para aplicacoes performaticas.
// lib/data.ts
// Cache por tempo - revalida a cada hora
export async function buscarCategorias() {
const response = await fetch('https://api.exemplo.com/categorias', {
next: { revalidate: 3600 } // 1 hora em segundos
});
return response.json();
}
// Cache estatico - revalida apenas em build
export async function buscarPaginasEstaticas() {
const response = await fetch('https://api.exemplo.com/paginas', {
cache: 'force-cache'
});
return response.json();
}
// Sem cache - sempre busca dados frescos
export async function buscarNotificacoes() {
const response = await fetch('https://api.exemplo.com/notificacoes', {
cache: 'no-store'
});
return response.json();
}
// Cache com tags para invalidacao seletiva
export async function buscarProdutosPorCategoria(categoriaId: string) {
const response = await fetch(
`https://api.exemplo.com/categorias/${categoriaId}/produtos`,
{
next: {
revalidate: 300, // 5 minutos
tags: [`categoria-${categoriaId}`, 'produtos']
}
}
);
return response.json();
}// app/admin/produtos/actions.ts
'use server';
import { revalidateTag, revalidatePath } from 'next/cache';
export async function atualizarProduto(produtoId: string, dados: FormData) {
await db.query('UPDATE produtos SET ... WHERE id = ?', [produtoId]);
// Invalida cache especifico
revalidateTag(`produto-${produtoId}`);
revalidateTag('produtos');
// Ou invalida por caminho
revalidatePath('/produtos');
revalidatePath(`/produtos/${produtoId}`);
}
export async function deletarProduto(produtoId: string) {
await db.query('DELETE FROM produtos WHERE id = ?', [produtoId]);
// Invalida todos os caches relacionados a produtos
revalidateTag('produtos');
revalidatePath('/produtos');
}Erros Comuns e Como Evitar
Alguns erros sao frequentes ao trabalhar com Server Components pela primeira vez.
Erro 1: Usar Hooks em Server Components
// ERRADO - hooks nao funcionam em Server Components
export default async function Pagina() {
const [estado, setEstado] = useState(''); // Erro!
// ...
}
// CORRETO - extrair para Client Component
'use client';
export function ComponenteInterativo() {
const [estado, setEstado] = useState('');
// ...
}Erro 2: Passar Funcoes como Props para Client Components
// ERRADO - funcoes nao sao serializaveis
export default function ServerComponent() {
const handleClick = () => console.log('click');
return <ClientComponent onClick={handleClick} />; // Erro!
}
// CORRETO - usar Server Actions
// actions.ts
'use server';
export async function handleSubmit(formData: FormData) {
// ...
}
// page.tsx
import { handleSubmit } from './actions';
export default function ServerComponent() {
return (
<form action={handleSubmit}>
{/* ... */}
</form>
);
}Se voce quer dominar mais tecnicas avancadas de frontend, recomendo conferir o artigo CSS Moderno em 2025 onde exploramos as novidades que complementam perfeitamente o React.

