Next.js: Server Components e Client Components — Como Usar, Como Funcionam e Para Que Servem
17 de junho de 2026 · por Adriel Silva
Introdução
O Next.js App Router trouxe uma mudança fundamental na forma como construímos aplicações React. Com a introdução dos React Server Components (RSC), temos agora um novo paradigma de renderização que separa claramente o que acontece no servidor do que acontece no cliente.
Essa distinção não é apenas técnica — ela impacta diretamente a performance, a experiência do desenvolvedor e a forma como pensamos sobre a arquitetura dos nossos componentes. Entender quando usar Server Components e quando usar Client Components é uma das habilidades mais importantes para quem trabalha com o App Router do Next.js.
Neste artigo, vamos explorar em profundidade as diferenças entre os dois tipos de componentes, ver exemplos práticos de código e aprender as melhores práticas para compor aplicações eficientes e bem estruturadas.
O que são Server Components?
Server Components são componentes React que executam exclusivamente no servidor. Eles nunca enviam JavaScript para o cliente — apenas o HTML resultante da renderização é enviado ao navegador. No App Router do Next.js, todos os componentes são Server Components por padrão.
Isso representa uma mudança significativa em relação ao modelo anterior, onde todo o código React era enviado ao cliente para hidratação. Com Server Components, apenas o resultado da renderização trafega pela rede.
As principais características dos Server Components são:
- Executam apenas no servidor
- Não adicionam JavaScript ao bundle do cliente
- Podem acessar banco de dados, APIs e variáveis de ambiente diretamente
- Não podem usar hooks como
useStateouuseEffect - Não podem usar eventos do browser (
onClick,onChange, etc.)
Por poderem acessar recursos do servidor diretamente — como bancos de dados, sistemas de arquivos e variáveis de ambiente secretas — os Server Components simplificam muito a busca de dados, eliminando a necessidade de criar rotas de API intermediárias para operações que não precisam de interatividade no cliente.
O que são Client Components?
Client Components são os componentes React que conhecemos do modelo tradicional. Eles são pré-renderizados no servidor (para o HTML inicial) e depois hidratados no cliente, onde o JavaScript assume o controle e torna o componente interativo.
Para marcar um componente como Client Component no App Router, basta adicionar a diretiva 'use client' no topo do arquivo. Essa diretiva sinaliza ao Next.js que tudo naquele arquivo (e suas importações) faz parte do bundle do cliente.
As principais características dos Client Components são:
- Marcados com
'use client'no topo do arquivo - Podem usar hooks do React (
useState,useEffect,useRef, etc.) - Podem acessar APIs do browser (
window,localStorage, etc.) - Podem usar event handlers (
onClick,onChange, etc.) - Adicionam JavaScript ao bundle do cliente
Client Components são essenciais para qualquer parte da interface que precise de interatividade, estado local ou acesso a APIs exclusivas do navegador. A chave é usá-los de forma cirúrgica, apenas onde realmente necessário.
Quando usar cada um?
A decisão entre Server Component e Client Component deve ser guiada pela pergunta: esse componente precisa de interatividade ou de APIs do browser?
Use Server Components quando:
- Precisar buscar dados de um banco de dados ou API externa
- Estiver construindo layouts estáticos ou semi-estáticos
- O componente for importante para SEO e precisa ser renderizado com conteúdo completo
- O componente não tiver nenhuma interatividade com o usuário
- Precisar acessar variáveis de ambiente secretas ou recursos do servidor
Use Client Components quando:
- Precisar de formulários interativos com validação em tempo real
- Estiver implementando animações ou transições controladas por estado
- O componente precisar de estado local (
useState) - Precisar acessar APIs do browser como
localStorage,windowounavigator - Estiver usando bibliotecas de terceiros que dependem de APIs do browser
Uma boa regra prática: comece sempre com Server Components e adicione 'use client' apenas quando encontrar uma necessidade real de interatividade ou APIs do browser.
Exemplos práticos de código
Vamos ver como esses conceitos se aplicam na prática com três exemplos concretos.
Exemplo 1: Server Component buscando dados
Um Server Component pode ser uma função async, o que permite usar await diretamente no corpo do componente, sem precisar de useEffect ou gerenciamento de estado:
// app/posts/page.tsx — Server Component (sem 'use client')
async function getPosts() {
const res = await fetch('https://api.exemplo.com/posts');
if (!res.ok) throw new Error('Falha ao buscar posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<main>
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</main>
);
}Perceba que não há useEffect nem useState — a busca de dados acontece diretamente no servidor, antes de qualquer HTML ser enviado ao cliente.
Exemplo 2: Client Component com interatividade
Quando precisamos de estado e interatividade, usamos 'use client' no topo do arquivo:
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Contagem: {count}</p>
<button onClick={() => setCount(count + 1)}>
Incrementar
</button>
<button onClick={() => setCount(0)}>
Resetar
</button>
</div>
);
}Este componente adiciona JavaScript ao bundle do cliente, mas é necessário para a interatividade. O importante é que ele seja pequeno e focado.
Exemplo 3: Compondo Server e Client Components
O padrão mais poderoso é compor os dois tipos juntos. Um Server Component pode importar e renderizar um Client Component, passando dados como props:
// app/dashboard/page.tsx — Server Component
import { Counter } from '@/components/Counter';
async function getUserData(id: string) {
// Acesso direto ao banco — só possível no servidor
return await db.users.findUnique({ where: { id } });
}
export default async function DashboardPage() {
const user = await getUserData('123');
return (
<main>
<h1>Olá, {user.name}!</h1>
{/* Counter é Client Component — recebe dados do servidor como props */}
<Counter initialCount={user.savedCount} />
</main>
);
}Os dados são buscados no servidor e passados como props simples para o Client Component — sem chamadas de API adicionais, sem loading states desnecessários.
O padrão de composição
Um dos conceitos mais importantes — e frequentemente mal compreendidos — do App Router é o padrão de composição entre Server e Client Components.
A regra fundamental
Você não pode importar um Server Component dentro de um Client Component. Isso faz sentido: o Client Component roda no browser, onde não há acesso ao servidor. Se você tentar importar um Server Component dentro de um arquivo com 'use client', ele será tratado como um Client Component automaticamente.
A solução: children e props
O que você pode fazer é passar Server Components como children ou props para Client Components. Essa é uma distinção crucial:
// app/page.tsx — Server Component
import { Modal } from '@/components/Modal'; // Client Component
import { HeavyContent } from '@/components/HeavyContent'; // Server Component
export default function Page() {
return (
<Modal>
<HeavyContent /> {/* Server Component passado como children */}
</Modal>
);
}
// components/Modal.tsx — Client Component
'use client';
import { useState } from 'react';
export function Modal({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(true);
if (!isOpen) return null;
return (
<div className="modal">
{children}
<button onClick={() => setIsOpen(false)}>Fechar</button>
</div>
);
}Nesse padrão, o HeavyContent continua sendo um Server Component — ele é renderizado no servidor e seu resultado (HTML) é passado como children para o Modal. O Client Component não precisa saber nada sobre como o conteúdo foi gerado.
Esse modelo mental — dois mundos que se comunicam através de props e children — é fundamental para arquitetar aplicações eficientes com o App Router.
Impacto na performance
A separação entre Server e Client Components tem impactos diretos e mensuráveis na performance da aplicação.
Redução do bundle JavaScript
Cada Server Component que você cria é código que nunca vai para o cliente. Em aplicações grandes, isso pode representar uma redução significativa no tamanho do bundle JavaScript, resultando em carregamentos mais rápidos, especialmente em dispositivos móveis com conexões lentas.
Melhoria no TTFB e Core Web Vitals
Como os Server Components buscam dados diretamente no servidor — sem roundtrips adicionais do cliente — o HTML inicial pode ser gerado e enviado mais rapidamente, melhorando o Time to First Byte (TTFB). Isso tem efeito cascata nas Core Web Vitals:
- LCP (Largest Contentful Paint): conteúdo principal renderizado no servidor aparece mais rápido
- INP (Interaction to Next Paint): menos JavaScript no cliente significa menos trabalho para o thread principal
- CLS (Cumulative Layout Shift): HTML completo do servidor reduz shifts causados por hidratação assíncrona
Além disso, com Server Components você pode buscar dados em paralelo usando Promise.all, fazer queries diretamente ao banco de dados sem overhead de API, e aproveitar o cache do Next.js de forma granular por componente.
Dicas práticas
Para aplicar esses conceitos de forma eficiente no seu dia a dia, siga estas boas práticas:
- Comece com Server Components por padrão — só adicione
'use client'quando necessário - Adicione
'use client'apenas quando necessário — interatividade, hooks ou APIs do browser - Mantenha Client Components pequenos e no final da árvore de componentes
- Prefira passar dados como props de Server para Client Components — evite buscar os mesmos dados duas vezes
- Use
Suspensepara loading states e streaming de conteúdo - Evite "contaminar" Server Components com lógica de cliente
- Use o React DevTools para identificar quais componentes estão no bundle do cliente
Conclusão
O modelo mental mais importante para trabalhar com o App Router do Next.js é pensar na árvore de componentes como dois mundos distintos: o mundo do servidor e o mundo do cliente.
No mundo do servidor, vivem os componentes que buscam dados, acessam recursos protegidos e geram HTML estático. No mundo do cliente, vivem os componentes que respondem a interações do usuário, gerenciam estado local e acessam APIs do browser.
A chave para uma arquitetura eficiente é projetar cada componente para viver no mundo certo. Isso significa adotar a abordagem server-first do App Router, empurrar a fronteira do cliente o mais para baixo possível na árvore, e usar o padrão de composição com children e props para conectar os dois mundos de forma elegante.
Quando você internaliza esse modelo mental, o desenvolvimento com Next.js App Router se torna mais intuitivo, suas aplicações ficam mais performáticas e a experiência dos seus usuários melhora significativamente. O futuro do desenvolvimento React é server-first — e o App Router é a melhor expressão desse paradigma disponível hoje.