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. 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. 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, e o bundle do cliente encolhe.