Marcos N
← Voltar ao blog

Next.js e arquitetura

Server e Client Components no Next.js: quando usar

Entenda Server e Client Components no Next.js com exemplos em TypeScript e decisões práticas para reduzir JavaScript sem comprometer a experiência.

Marcos N6 min de leitura
Server e Client Components no Next.js: quando usar

Você coloca 'use client' no topo da página para resolver um erro de useState. Funciona. Depois, a página ganha busca de dados, bibliotecas e conteúdo que não precisa de interação. Server e Client Components no Next.js ajudam a definir uma fronteira mais cuidadosa: o que precisa executar no navegador e o que pode permanecer no servidor?

A pergunta de arquitetura também é de experiência. Antes de clicar em qualquer coisa, a pessoa quer entender a página. Quando interage, espera uma resposta rápida. Essas duas necessidades não exigem que todo o código execute no mesmo ambiente.

Como Server e Client Components funcionam no Next.js

No App Router, páginas e layouts são Server Components por padrão. Componentes que precisam de estado, eventos ou APIs do navegador entram na fronteira cliente com 'use client'. Essa diretiva define uma fronteira no grafo de módulos; seus imports também precisam ser adequados ao ambiente cliente. Documentação de Server e Client Components.

“Client” não significa necessariamente HTML vazio no carregamento inicial. O Next.js pode gerar HTML inicial também para esses componentes e depois hidratá-los para ativar a interação. A distinção relevante é quais módulos e responsabilidades precisam estar no cliente.

Eu uso uma pergunta simples: qual comportamento exige que este componente seja interativo? Muitas vezes, a resposta aponta para um botão ou filtro, não para a página inteira.

Um catálogo com uma interação pequena

Imagine uma página que apresenta um serviço e permite selecionar uma opção para pedir informações. O texto principal pode ser renderizado no servidor. A seleção fica em um componente cliente.

app/servicos/page.tsx
import { ServiceSelector } from './ServiceSelector';

export default function ServicesPage() {
  return (
    <main>
      <h1>Design e desenvolvimento para sua empresa</h1>
      <p>Escolha o tipo de projeto para iniciar a conversa.</p>
      <ServiceSelector
        options={[
          { id: 'site', label: 'Site institucional' },
          { id: 'produto', label: 'Interface de produto' },
        ]}
      />
    </main>
  );
}
app/servicos/ServiceSelector.tsx
'use client';

import { useId, useState } from 'react';

type Option = { id: string; label: string };

export function ServiceSelector({ options }: { options: Option[] }) {
  const selectId = useId();
  const [selectedId, setSelectedId] = useState('');
  const selected = options.find(option => option.id === selectedId);

  return (
    <div>
      <label htmlFor={selectId}>Tipo de projeto</label>
      <select
        id={selectId}
        value={selectedId}
        onChange={event => setSelectedId(event.target.value)}
      >
        <option value="">Selecione uma opção</option>
        {options.map(option => (
          <option key={option.id} value={option.id}>
            {option.label}
          </option>
        ))}
      </select>
      <p role="status">
        {selected ? `Você escolheu: ${selected.label}.` : 'Escolha seu projeto.'}
      </p>
    </div>
  );
}

Esse exemplo não envia formulário nem cria um orçamento. Ele isola uma interação realista e deixa explícito o contrato de dados. Os objetos enviados contêm apenas identificador e rótulo; nenhum registro interno ou informação privada precisa acompanhar a interface.

Eu começaria com o select nativo. Substituí-lo por um menu visual exige implementar e testar navegação de teclado, seleção e anúncio de estado. A decisão estética tem um custo de engenharia que precisa trazer um benefício perceptível.

O que atravessa a fronteira?

Props de Server para Client Components precisam usar valores serializáveis pelo React. Isso não se resume exatamente aos tipos aceitos por JSON; a documentação descreve o conjunto suportado. Funções arbitrárias e instâncias de classes não são contratos adequados para atravessar essa fronteira. Diretiva use client no React.

Minha recomendação é desenhar um DTO pequeno para cada interação: os dados de que a tela realmente precisa. Além de reduzir acoplamento, isso facilita revisar quais informações chegam ao navegador.

Mover a consulta para o servidor não autoriza enviar seu resultado inteiro ao cliente. Tudo que você entrega em props deve ser tratado como acessível à pessoa que usa o navegador.

Composição não é o mesmo que importar tudo no cliente

É possível passar conteúdo produzido no servidor como children de um componente cliente. Isso é diferente de importar esse módulo diretamente a partir do arquivo marcado com 'use client'. Padrões de composição do Next.js.

No portfólio, essa ideia aparece na página que compõe as seções dentro de um componente responsável pela cena de scroll. A existência de um invólucro interativo não exige transformar manualmente todo o conteúdo em código cliente.

Eu manteria o estado perto de quem o consome. Se apenas um filtro precisa saber a opção selecionada, colocá-lo em um provider global aumenta a superfície de dependência sem melhorar a tarefa.

Como escolher em um projeto real

SituaçãoPonto de partidaCuidado
Texto, metadados e conteúdo do artigoServidorNão esconder o conteúdo atrás de uma interação
Filtro local com resposta imediataClienteAvaliar volume de dados enviado
Consulta com credenciais privadasServidorAutorização continua necessária
Mapa ou editor que usa windowClienteReservar espaço e avaliar carregamento
Situação
Texto, metadados e conteúdo do artigo
Ponto de partida
Servidor
Cuidado
Não esconder o conteúdo atrás de uma interação
Situação
Filtro local com resposta imediata
Ponto de partida
Cliente
Cuidado
Avaliar volume de dados enviado
Situação
Consulta com credenciais privadas
Ponto de partida
Servidor
Cuidado
Autorização continua necessária
Situação
Mapa ou editor que usa window
Ponto de partida
Cliente
Cuidado
Reservar espaço e avaliar carregamento

Servidor não é sinônimo automático de rapidez. Uma consulta lenta ainda atrasa conteúdo. Cliente não é sinônimo automático de problema. Um editor interativo precisa de lógica local. O trabalho é dividir responsabilidades conforme o produto.

Quando eu usaria isso em um projeto real?

Em sites institucionais e blogs, começaria com conteúdo no servidor e acrescentaria pequenas áreas interativas. Num painel de operação, aceitaria uma região cliente maior quando os controles compartilham estado e precisam responder juntos.

Antes de refatorar, mediria o bundle da rota e testaria a interação em um dispositivo modesto. Se o gargalo está em uma imagem enorme, mover um botão de lugar na árvore não resolverá o carregamento percebido.

Também revisaria os estados de erro e carregamento. Dividir corretamente o código não ajuda se a pessoa vê uma tela vazia sem entender se a solicitação falhou.

Erros comuns

Adicionar use server para criar um Server Component

A diretiva 'use server' identifica Server Functions; não é a contraparte necessária de 'use client' para componentes. No App Router, o padrão já atende componentes do servidor. Referência de use server.

Ler window durante a renderização inicial

Mesmo um Client Component pode participar da pré-renderização. APIs exclusivas do navegador precisam ser acessadas em um momento apropriado, como um efeito ou evento, e o primeiro resultado renderizado precisa ser consistente.

Acreditar que a fronteira resolve autorização

Uma função no servidor ainda precisa verificar permissões para acessar ou alterar dados. A escolha de ambiente não substitui regras de segurança.

Conclusão

Server e Client Components no Next.js permitem aproximar o código do lugar onde ele faz sentido. Minha regra prática é manter conteúdo e dados perto do servidor e levar ao navegador a interação necessária. A fronteira ideal deixa a experiência rápida e o código compreensível, sem transformar uma preferência arquitetural em dogma.