React Server Components vs Client Components: Quando Usar Cada Um no Next.js
O erro que custa kilobytes e latência
No App Router do Next.js, todo componente é Server Component por padrão. Adicionar "use client" no topo de um arquivo parece inofensivo: uma linha, sem erro no terminal, sem warning. O problema aparece depois, quando o bundle do cliente cresce sem necessidade, quando dados que poderiam ser buscados no servidor passam a exigir useEffect + loading state, e quando a árvore de componentes vira uma mistura confusa de responsabilidades.
A distinção entre Server Components (RSC) e Client Components não é cosmética. Ela define onde o código executa, o que vai para o bundle do navegador, e como os dados fluem pela aplicação. Se você está trabalhando com o App Router do Next.js, essa decisão impacta cada rota que você cria.
Como o Next.js decide o que roda onde
O runtime do Next.js separa dois ambientes de execução: o servidor (Node.js ou Edge runtime) e o cliente (navegador). Server Components executam exclusivamente no servidor. O resultado é HTML serializado via um protocolo chamado RSC Payload, que o React no cliente hidrata sem precisar do código-fonte do componente.
Client Components executam nos dois ambientes: primeiro no servidor (para gerar o HTML inicial via SSR), depois no cliente (para hidratar e tornar o componente interativo). O código inteiro do Client Component, incluindo dependências, vai para o bundle JavaScript do navegador.
// app/products/page.tsx
// Server Component por padrão: sem "use client", sem useState, sem useEffect.
// O Next.js executa isso no servidor e envia HTML + RSC Payload.
import { ProductList } from "./product-list";
import { db } from "@/lib/database";
export default async function ProductsPage() {
// Acesso direto ao banco, sem API intermediária.
// Isso só funciona porque o código nunca chega ao navegador.
const products = await db.product.findMany({
where: { active: true },
orderBy: { createdAt: "desc" },
take: 50,
});
return (
<main>
<h1>Produtos</h1>
<ProductList products={products} />
</main>
);
}
// app/products/product-list.tsx
"use client";
// Este componente precisa de "use client" porque usa useState para filtro local.
// Ele recebe os dados já carregados via props: o fetch aconteceu no servidor.
import { useState } from "react";
type Product = {
id: string;
name: string;
price: number;
category: string;
};
export function ProductList({ products }: { products: Product[] }) {
const [filter, setFilter] = useState("");
const filtered = products.filter((p) =>
p.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="Filtrar por nome"
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<ul>
{filtered.map((product) => (
<li key={product.id}>
{product.name} — R$ {product.price.toFixed(2)}
</li>
))}
</ul>
</div>
);
}
A fronteira entre os dois mundos é o "use client". Tudo abaixo dessa diretiva, incluindo componentes filhos importados, entra no bundle do cliente. Tudo acima permanece no servidor.
Matriz de decisão: Server ou Client?
A escolha não depende de gosto. Depende do que o componente faz.
| Necessidade do componente | Server Component | Client Component |
|---|---|---|
| Buscar dados de banco/API interna | Sim (acesso direto, sem fetch extra) | Não (exigiria API route + useEffect) |
Usar useState, useEffect, useReducer | Não | Sim |
| Manipular eventos do DOM (onClick, onChange) | Não | Sim |
| Acessar browser APIs (localStorage, geolocation) | Não | Sim |
| Renderizar conteúdo estático ou markdown | Sim (zero JS no cliente) | Desnecessário |
| Usar bibliotecas pesadas de UI (editores, gráficos) | Evitar (o peso vai pro bundle) | Sim (mas isole o componente) |
| Acessar variáveis de ambient |
Leia o artigo completo em https://www.vivodecodigo.com.br/nextjs/react-server-components-vs-client-components-quando-usar-nextjs