React Server Components chegaram com promessas grandes, e com uma confusão proporcional. Depois de meses usando RSC em produção com Next.js App Router, a pergunta mais comum que recebo ainda é: "Mas quando eu uso Server Component mesmo?"

Este artigo não é introdução — assume que você já sabe o que é RSC. É um guia de decisão prático: quando usar o quê, por que, e os erros que quase todo mundo comete na transição.

O modelo mental correto

O erro mais comum é pensar em RSC como "componentes que rodam no servidor" e Client Components como "componentes que rodam no cliente". Essa descrição está correta mas incompleta de uma forma que gera más decisões.

A forma mais útil de pensar:

  • Server Components são funções que rodam apenas no servidor, têm acesso direto a dados (banco, filesystem, APIs internas), e nunca são incluídas no bundle do cliente. Elas produzem HTML + RSC payload que é enviado ao cliente.
  • Client Components são funções que rodam no cliente (e também no servidor para hidratação). Têm acesso a state, effects, event handlers, e APIs do browser. São incluídas no bundle.

A implicação prática: Server Components não carregam JavaScript no cliente. Isso significa que qualquer lógica, dependência, ou processamento feito num Server Component tem custo zero de bundle.

Quando usar Server Components

Use Server Components quando o componente:

  • Busca dados diretamente (banco, API, filesystem)
  • Usa dependências grandes que não precisam ser interativas (marked, sharp, parsers pesados)
  • Renderiza conteúdo estático ou com dados que vêm do servidor
  • Precisa de acesso a variáveis de ambiente secretas
  • É uma camada de composição que não tem estado ou interatividade própria
// Server Component — busca direta, sem bundle no cliente
async function ProductPage({ id }: { id: string }) {
  const product = await db.product.findUnique({ where: { id } })

  return (
    <div>
      <h1>{product.name}</h1>
      <ProductActions product={product} /> {/* Client Component */}
    </div>
  )
}

Quando usar Client Components

Use Client Components quando o componente precisa de:

  • Estado local (useState, useReducer)
  • Efeitos (useEffect, useLayoutEffect)
  • Event handlers (onClick, onChange, etc.)
  • APIs do browser (localStorage, window, navigator)
  • Bibliotecas que dependem de APIs do browser
'use client'

import { useState } from 'react'

function ProductActions({ product }: { product: Product }) {
  const [quantity, setQuantity] = useState(1)

  return (
    <div>
      <input
        type="number"
        value={quantity}
        onChange={e => setQuantity(Number(e.target.value))}
      />
      <button onClick={() => addToCart(product.id, quantity)}>
        Adicionar ao carrinho
      </button>
    </div>
  )
}

Os erros mais comuns

1. Marcar tudo como 'use client' por precaução

O erro mais comum na transição. Se você adiciona 'use client' em todos os componentes "só para funcionar", você perdeu o benefício central dos RSC — redução de bundle. Pior: um Server Component importado dentro de um Client Component se torna um Client Component também.

2. Passar objetos não-serializáveis para Client Components

Server Components podem passar props para Client Components, mas apenas props serializáveis — strings, números, arrays, objetos simples. Funções, instâncias de classe, e objetos com métodos não podem ser passados. Isso pega muita gente de surpresa.

// ❌ Erro: funções não podem ser passadas de Server para Client
<ClientComponent handler={someServerFunction} />

// ✅ Correto: defina o handler no próprio Client Component
// ou use Server Actions para callbacks

3. Confundir Server Actions com Server Components

Server Actions são funções assíncronas que rodam no servidor e podem ser chamadas de Client Components. Elas são o mecanismo para mutações — não para busca de dados. Use Server Components para leitura, Server Actions para escrita.

4. Waterfall de dados não intencional

Com async/await em Server Components, é fácil criar waterfalls:

// ❌ Waterfall — espera um, depois o outro
const user = await fetchUser(id)
const orders = await fetchOrders(user.id)

// ✅ Paralelo — busca simultânea
const [user, orders] = await Promise.all([
  fetchUser(id),
  fetchOrders(id)
])

O padrão de composição que funciona

O padrão mais eficiente é manter o máximo possível como Server Components, e empurrar Client Components para as folhas da árvore — os componentes mais próximos da interatividade real.

Uma página típica bem estruturada com RSC tem: Server Component como raiz (busca dados, composição), Client Components só onde há interatividade, e Server Components aninhados para sub-seções que precisam de dados mas não de interatividade.

Quando você interioriza esse padrão, a arquitetura começa a fluir naturalmente, e o bundle do cliente encolhe de forma visível.