Server Components e Server Actions no React/Next

Uma explicação prática sobre o que são Server Components e Server Actions, a diferença entre componentes client e server, e como o Next executa funções do servidor a partir do navegador.

react nextjs tecnico 6 min de leitura

Server Components e Server Actions não são conceitos exclusivos do Next, são conceitos do React. O Next só implementa isso muito bem, e é por isso que fica mais fácil entender usando ele como referência. Vou explicar os dois conceitos na prática, com um projeto simples em Next com Prisma conectado a um banco de dados.

O que são Server Components

No React, quase tudo gira em torno do conceito de componentes. Um botão é um componente, um texto é um componente, uma listagem é um componente. A própria tela é um componente, e dentro dela você vai encaixando outros componentes menores.

Por padrão, e por muitos anos, o React funcionou assim: você escreve o componente, mas ele não é renderizado no servidor, ele é transportado como código JavaScript e só toma forma dentro do navegador do usuário. Chega pro navegador uma div vazia e um monte de JavaScript que constrói o site inteiro ali dentro. Isso é o que chamamos de client component: um componente que roda no cliente, ou seja, no navegador do usuário.

export default function Home() {
  return <div>Olá mundo</div>
}

No React puro, por padrão, todo componente é assim. No Next é o contrário: por padrão, todo componente é um server component.

Quando um componente é server component, o próprio Next monta ele inteiro no servidor: consultas a banco de dados, formatação de texto, qualquer lógica que o componente precise. Ele monta o HTML final ali no servidor e manda só o resultado pronto pro navegador. O usuário nunca recebe o componente "cru", recebe o HTML já processado.

Para transformar um componente em client component, basta adicionar uma diretiva no topo do arquivo:

"use client"
 
export default function Home() {
  return <div>Olá mundo</div>
}

Com isso, o Next passa a tratar aquele componente da forma padrão do React: ele é montado no navegador do usuário, não no servidor.

Por que isso importa na prática

A diferença fica clara quando você tenta adicionar interação. Um clique só existe no navegador do usuário, então só faz sentido dentro de um componente client. Se você tentar fazer isso num server component:

export default function Home() {
  function handleClick() {
    alert("Cliquei")
  }
 
  return (
    <div>
      Olá mundo
      <button onClick={handleClick}>Clique aqui</button>
    </div>
  )
}

O Next acusa erro na hora: Event handlers cannot be passed to Client Component props. Faz sentido, esse componente é montado no servidor, e o servidor não tem como saber quando o usuário vai clicar em algo, porque quem clica é o navegador dele, não o servidor.

A solução é isolar essa parte interativa num componente separado, marcado como client:

// components/botao.tsx
"use client"
 
export const Botao = () => {
  function handleClick() {
    alert("Cliquei")
  }
 
  return <button onClick={handleClick}>Clique aqui</button>
}

E usar esse componente dentro da página:

import { Botao } from "@/components/botao"
 
export default function Home() {
  return (
    <div>
      Olá mundo
      <Botao />
    </div>
  )
}

Agora o Next renderiza a página inteira no servidor, exceto o botão: esse pedaço específico, com todo o JavaScript necessário, é enviado pro navegador para funcionar por lá. Um componente server pode conter um componente client dentro dele sem problema nenhum, o inverso é que não funciona.

Server Components consultando banco de dados

A grande utilidade de um server component é justamente essa: como ele roda no servidor, ele tem acesso direto a tudo que só existe no servidor, como a conexão com o banco de dados.

Um exemplo prático, com uma função que busca uma lista de tarefas usando Prisma:

// services/tasks.ts
import { prisma } from "@/lib/prisma"
 
export async function getAllTasks() {
  const tasks = await prisma.task.findMany()
  return tasks
}

E usando essa função direto na página, sem precisar de API, sem useEffect, sem estado de loading:

import { getAllTasks } from "@/services/tasks"
 
export default async function Home() {
  const tasks = await getAllTasks()
 
  return (
    <div>
      <h1 className="text-xl mb-4">Olá mundo</h1>
      {tasks.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  )
}

Quando essa página é acessada, o Next executa a função no servidor, consulta o banco, monta a lista completa em HTML e manda o resultado pronto pro navegador. Se esse mesmo código estivesse num client component, simplesmente não funcionaria, porque não existe acesso a banco de dados dentro do navegador do usuário.

O que são Server Actions

Server Actions resolvem o problema inverso: como executar, a partir de um componente client, uma função que só pode rodar no servidor.

Para criar uma, você define uma função normal e marca ela com "use server" no topo:

// actions/task-count.ts
"use server"
 
import { getAllTasks } from "@/services/tasks"
 
export async function getTaskCount() {
  const tasks = await getAllTasks()
  return tasks.length
}

Essa função consulta o banco de dados, então só pode rodar no servidor. Ainda assim, dá para chamar ela direto de dentro de um componente client:

"use client"
 
import { getTaskCount } from "@/actions/task-count"
 
export const Botao = () => {
  async function handleClick() {
    const count = await getTaskCount()
    alert(count)
  }
 
  return <button onClick={handleClick}>Clique aqui</button>
}

Ao clicar no botão, aparece a quantidade real de tarefas cadastradas no banco, mesmo esse componente rodando inteiramente no navegador do usuário.

Como isso funciona por baixo dos panos

Abrindo a aba Network do navegador e clicando no botão, dá para ver uma requisição POST sendo feita pro próprio site, sem nenhum corpo visível na aba de request nem de response. É aí que mora o segredo.

O funcionamento é basicamente esse:

  1. Você cria uma função e marca ela com "use server", dizendo que ela sempre roda no servidor.
  2. Dentro de um componente client, você chama essa função normalmente, como se fosse local.
  3. Como o Next sabe que essa função é uma server action, ele não executa ela no navegador. Em vez disso, ele faz uma requisição pro servidor, pedindo para executar aquela função específica, com os parâmetros que você passou.
  4. O servidor executa a função, monta o resultado e devolve pro navegador.
  5. O Next entrega esse resultado exatamente como se a função tivesse retornado ali mesmo, no componente client.

Na prática é o mesmo efeito de criar uma API e consultar ela a partir do frontend, só que o Next monta toda essa comunicação sozinho, sem você precisar criar rota, sem precisar fazer fetch manual, sem precisar tratar serialização.

Resumindo

Server component é um componente que é montado inteiramente no servidor, ideal para buscar dados e montar HTML, mas sem nenhuma interação direta do usuário. Client component é o componente tradicional do React, que roda no navegador e permite clique, estado, eventos. Server action é uma função marcada para rodar sempre no servidor, que pode ser chamada diretamente de dentro de um componente client, com o Next cuidando da comunicação entre os dois lados por trás dos panos.

Com esses dois conceitos combinados, dá para montar boa parte da comunicação entre frontend e backend usando só funções, sem precisar criar uma API completa e separada para cada operação simples.