Se você tá começando no Next.js e ainda fica perdido quando aparece aquele erro ‘You’re using useState in a Server Component‘, senta que esse artigo é pra você. A real é que entender Server e Client Components é crucial entre escrever código que funciona e código que você nem sabe por que funciona.
E com o Next.js, algumas coisas ficaram ainda mais claras.
O Que Mudou no Next.js?
Antes de falar de Server vs Client, preciso te contar o que tá novo no Next.js porque isso impacta diretamente como você vai trabalhar:
Turbopack agora é padrão. Esquece webpack – a menos que você tenha um setup muito específico, você vai usar Turbopack. E isso significa builds até 5x mais rápidos e um Fast Refresh até 10x mais veloz. Sim, você leu certo.
Cache Components mudaram o jogo. Antes, o Next.js decidia sozinho o que cachear. Agora, com a diretiva use cache, você escolhe. Tudo é dinâmico por padrão, e você opta por cachear o que faz sentido. Sem surpresas.
proxy.ts substituiu middleware.ts. A ideia é deixar mais claro onde roda o quê. O middleware antigo ainda existe pra quem usa Edge Runtime, mas o padrão agora é o proxy.ts rodando no Node.js.
React 19.2 veio junto, com View Transitions, useEffectEvent e outras firulas que melhoram a experiência do usuário sem você precisar quebrar a cabeça.
Beleza, contexto dado. Agora vamos pro que interessa.
Server Component: O Padrão que Você Não Sabia que Queria
Aqui vai a primeira coisa que você precisa gravar: no Next.js, todo componente é Server por padrão. Você não precisa fazer nada. Se você criar um arquivo Header.tsx e não colocar 'use client' no topo, ele vai rodar no servidor.
E por que isso é bom? Porque Server Components não mandam JavaScript pro navegador. Zero. Nada.
Olha só esse componente básico:
// app/product/page.tsx
async function ListProducts() {
const products = await fetch('https://api.mystor.com/product')
.then(res => res.json());
return (
<div>
{products.map(product => (
<div key={product.id}>
<h2>{product.name}</h2>
<p>$ {product.price}</p>
</div>
))}
</div>
);
}Esse código roda só no servidor. O usuário recebe HTML pronto, renderizado. Nada de JavaScript sendo baixado, parseado e executado pra mostrar uma lista de produtos.
Vantagens? Várias:
- Bundle menor. Menos JavaScript pra baixar = página mais rápida;
- Segurança. Suas API keys, tokens, lógica sensível ficam no servidor. Nunca vazam pro cliente;
- SEO de graça. Como o HTML já vem pronto, os crawlers dos buscadores conseguem indexar tudo numa boa;
- Acesso direto ao banco. Você pode fazer
await db.query()direto no componente. Sem precisar criar uma API só pra isso.
Mas tem pegadinha. Server Components não podem ter interatividade. Sem onClick, sem useState, sem useEffect. Se você tentar usar, o Next.js vai gritar com você.
Client Component: Quando Você Precisa?
Agora vem a parte interativa. Se você precisa que o usuário clique num botão, digite num input, veja uma animação, você vai precisar de um Client Component.
E é simples: só colocar 'use client' no topo do arquivo:
// components/CartButton.tsx
'use client'
import { useState } from 'react'
export default function CartButton({ productId }: { productId: string }) {
const [loading, setLoading] = useState(false)
async function addToCart() {
setLoading(true)
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId })
})
setLoading(false)
}
return (
<button onClick={addToCart} disabled={loading}>
{loading ? 'Adicionando...' : 'Adicionar ao carrinho'}
</button>
)
}
Esse componente vai rodar no navegador. O JavaScript dele vai ser baixado, parseado e executado. E tá tudo bem, porque você precisa dessa interatividade.
Mas ó, tem um detalhe importante: Client Components ainda são renderizados no servidor primeiro. Isso confunde muita gente. O Next.js gera o HTML inicial no servidor (SSR) e depois ‘hidrata’ o componente no cliente, tornando ele interativo. Não é ‘client-only’, é ‘client-ready’.
O Erro Clássico: Misturar Tudo Errado
Aqui é onde a galera mais se perde. Imagina que você tem um Client Component e tenta importar um Server Component dentro dele:
// ERRADO
'use client'
import LikeButton from './LikeButton' // Client Component
import ProductList from './ProductList' // Server Component
export default function ProductPage() {
return (
<div>
<LikeButton />
<ProductList /> {/* Vai virar client component */}
</div>
)
}
O que acontece? Tudo vira Client Component. A diretiva 'use client' cria uma ‘fronteira’ – tudo que você importar dentro daquele arquivo é tratado como código de cliente.
A solução? Usar children:
// CERTO
// app/products/page.tsx (Server Component)
import LikeButton from './LikeButton' // Client Component
import ProductList from './ProductList' // Server Component
import Container from './Container' // Client Component
export default function ProductPage() {
return (
<Container>
<LikeButton />
<ProductList /> {/* Continua sendo Server Component */}
</Container>
)
}// components/Container.tsx (Client Component)
'use client'
export default function Container({ children }: { children: React.ReactNode }) {
return <div className='container'>{children}</div>
}Passando como children, você mantém o Server Component no servidor. É tipo você estar dizendo para o Next.js: “ó, esse pedaço aqui continua sendo server-side, só renderiza ele e me passa o HTML pronto”.
Quando Usar o Quê?
A real é que você deveria usar Server Components sempre que possível. E só partir pro Client Component quando você realmente precisar.
Use Server Component quando:
- Você tá buscando dados de API ou banco;
- Precisa esconder lógica sensível (tokens, keys);
- Quer reduzir o bundle JavaScript;
- Não tem interatividade (sem cliques, sem estado).
Use Client Component quando:
- Precisa de
useState,useEffect,useReducer, etc.; - Tem event handlers (
onClick,onChange); - Usa APIs do browser (
localStorage,window,navigator); - Precisa de bibliotecas que dependem de hooks do React.
E a combinação dos dois? Essa é a mágica do Next.js moderno. Você pode ter uma página Server Component que busca dados pesados do banco, e dentro dela ter uns botõezinhos Client Component pra curtida, carrinho, modal. O melhor dos dois mundos.
Next.js e o Novo Modelo de Cache
Uma coisa legal do Next.js é que agora o cache ficou mais explícito. Antes, você nunca sabia direito o que tava sendo cacheado. Agora, com use cache, você controla:
// Server Component com cache explícito
'use cache'
async function FeaturedProducts() {
const products = await fetch('https://api.mystor.com/featured')
.then(res => res.json())
return (
<div>
{products.map(p => <Card key={p.id} product={p} />)}
</div>
)
}
Sem use cache? Roda em request time, sempre fresco. Com use cache? Next.js cacheia e reutiliza. Simples assim.
E tem umas APIs novas também:
updateTag()– invalida um cache e já busca dados novos na mesma request;revalidateTag()– marca pra revalidar depois;refresh()– atualiza o router sem cache.
Tudo pensado pra você ter controle fino sobre quando e como os dados são atualizados.
Exemplo Real: Uma Loja Completa
Vamos ver como fica na prática:
// app/products/page.tsx (Server Component)
import BuyButton from './BuyButton'
import Filters from './Filters'
export default async function ProductsPage() {
// Busca dados no servidor
const products = await fetch('https://api.mystor.com/products')
.then(res => res.json())
return (
<div>
<h1>Our Products</h1>
{/* Client Component pra filtros interativos */}
<Filters />
<div className='grid'>
{products.map(product => (
<div key={product.id}>
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p>$ {product.price}</p>
{/* Client Component pra botão interativo */}
<BuyButton productId={product.id} />
</div>
))}
</div>
</div>
)
}
// components/BuyButton.tsx (Client Component)
'use client'
import { useState } from 'react'
export default function BuyButton({ productId }: { productId: string }) {
const [added, setAdded] = useState(false)
function handleBuy() {
// Lógica de adicionar ao carrinho
setAdded(true)
}
return (
<button onClick={handleBuy}>
{added ? '✓ Added' : 'Buy'}
</button>
)
}
// components/Filters.tsx (Client Component)
'use client'
import { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
export default function Filters() {
const router = useRouter()
const searchParams = useSearchParams()
const [category, setCategory] = useState(searchParams.get('category') || '')
function applyFilter() {
router.push(`/products?category=${category}`)
}
return (
<div>
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value=''>All</option>
<option value='electronics'>Electronics</option>
<option value='clothes'>Clothes</option>
</select>
<button onClick={applyFilter}>Filter</button>
</div>
)
}
Viu como funciona? A página principal é Server Component – ela busca os produtos direto do servidor. Os botões e filtros são Client Components porque precisam de interatividade. E todo mundo convivendo em paz.
O Erro que TODO Mundo Comete
Sabe qual é o erro mais comum? Marcar tudo como 'use client' sem necessidade.
// Desnecessário
'use client'
export default function Title() {
return <h1>Meu Site</h1>
}Cara, esse componente não precisa ser Client. Ele não usa estado, não tem evento, nada. Deixa ele como Server Component! Você vai mandar menos JavaScript pro navegador e o site vai carregar mais rápido.
A regra é simples: se você não usou useState, useEffect ou event handler, não precisa de 'use client'. Ponto.
Performance
Vamos falar sério sobre performance. Um Server Component com 50 linhas de código não adiciona nada ao bundle JavaScript. Um Client Component com as mesmas 50 linhas? Adiciona tudo.
E não é só tamanho de arquivo. É parsing, execução, hydration. Quanto mais Client Components, mais trabalho o navegador tem que fazer. E isso afeta principalmente usuários com celulares mais fracos ou internet lenta.
Por isso o Next.js 16 melhorou tanto o Turbopack. Porque mesmo com Client Components, você quer que o build seja rápido. Mas a melhor otimização ainda é: não envie JavaScript desnecessário.
Menos é Mais
No fim das contas, Server e Client Components são sobre escolher o lugar certo pra cada coisa. Não é sobre Server Component ser “melhor” que Client Component. É sobre usar cada um onde faz sentido.
Então para de tentar adivinhar. Se não precisa de interatividade, deixa no servidor. Se precisa, manda pro cliente. E usa children quando precisar misturar os dois. Simples assim.
E agora, sem desculpa pra continuar confuso. Bora codar.