Server Actions no Next.js 16 em 2026: Mutações, Revalidação e Perguntas de Entrevista

Como as Server Actions do Next.js 16 lidam com mutações, revalidação, estado de pendência, UI otimista e segurança, com as perguntas de entrevista que testam cada conceito.

Diagrama do fluxo de mutações e revalidação das Server Actions do Next.js

As Server Actions do Next.js transformam uma simples função assíncrona em um endpoint de mutação no servidor que um formulário pode chamar diretamente, sem rota de API, sem fetch no cliente e sem serialização manual de JSON. No Next.js 16, elas são a forma padrão de escrever mutações de dados e aparecem em quase toda entrevista sênior de React em 2026. Esta análise detalhada mostra como elas são executadas, como a revalidação propaga dados atualizados para a UI, como modelar estados de pendência e erro e as regras de segurança que derrubam a maioria dos desenvolvedores.

Definição em uma frase

Uma Server Action é uma função assíncrona marcada com "use server" que roda apenas no servidor e pode ser chamada de um componente cliente ou de um formulário como se fosse local, enquanto o Next.js cuida automaticamente da requisição de rede, da serialização dos argumentos e da proteção contra CSRF.

Como as Server Actions são executadas por baixo dos panos

Uma Server Action é uma função exclusiva do servidor invocada pela rede por meio de um endpoint gerado pelo compilador. Quando uma função carrega a diretiva "use server", o compilador do Next.js nunca envia o corpo dela para o navegador. Em vez disso, ele substitui a função por uma referência leve: um ID de ação em hash mapeado para uma rota POST gerada. Chamar a ação a partir do cliente envia uma requisição para essa rota, a função real roda no servidor e o resultado serializado retorna em streaming para quem a chamou.

Esse design tem três consequências que vale a pena guardar tanto para o código de produção quanto para entrevistas. Primeira: argumentos e valores de retorno precisam ser serializáveis, porque atravessam a rede como payloads codificados. Segunda: segredos permanecem no servidor, já que o corpo da função nunca é empacotado para o cliente. Terceira: ações vinculadas a um <form> funcionam mesmo antes da hidratação, e é por isso que o aprimoramento progressivo é um dos grandes atrativos. O guia oficial do Next.js sobre atualização de dados documenta o modelo de execução completo.

Toda Server Action vive em um arquivo ou bloco marcado com "use server". Uma mutação simples se parece com isto:

app/posts/actions.tstypescript
"use server"

import { revalidatePath } from "next/cache"
import { redirect } from "next/navigation"
import { PostService } from "@/lib/services/post-service"

export async function createPost(formData: FormData) {
  // FormData values arrive as strings; cast explicitly
  const title = formData.get("title") as string
  const body = formData.get("body") as string

  // Run business logic through the service layer, never the ORM directly
  const post = await PostService.create({ title, body })

  // Purge the cached list so the next request refetches it
  revalidatePath("/posts")

  // Send the user to the freshly created resource
  redirect(`/posts/${post.id}`)
}

A função lê o FormData enviado, persiste os dados por meio de um service, invalida a listagem em cache e redireciona. Nenhum JavaScript no cliente é necessário para nada disso.

Conectando uma Server Action a um formulário sem JavaScript no cliente

Passar a ação para a prop action do formulário é a integração mais simples. O Next.js serializa o envio em FormData e chama a função no servidor. Como isso usa o mecanismo nativo de submissão de formulários, funciona com o JavaScript desativado e evolui de forma transparente assim que a página é hidratada.

app/posts/new-post-form.tsxtsx
import { createPost } from "./actions"

export default function NewPostForm() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="body" required />
      <button type="submit">Publish</button>
    </form>
  )
}

O atributo name de cada campo se torna a chave lida por formData.get(). A referência de FormData na MDN explica como o navegador monta esse objeto a partir do formulário. Para um controle mais rico, o next/form estende o elemento nativo com navegação no cliente e prefetching, mantendo o mesmo contrato baseado em ações.

Gerenciando estados de pendência e erro com useActionState

Formulários reais precisam de feedback de validação e de um indicador de carregamento. O hook useActionState do React 19 envolve uma Server Action, encadeia um valor de estado a cada envio e expõe uma flag isPending. Ele substitui o antigo nome useFormState e retorna uma tupla com o estado atual, uma ação encapsulada e o booleano de pendência.

app/posts/new-post-form.tsxtsx
"use client"

import { useActionState } from "react"
import { createPost } from "./actions"

const initialState = { error: "" }

export default function NewPostForm() {
  // state carries the last return value; isPending tracks the in-flight request
  const [state, formAction, isPending] = useActionState(createPost, initialState)

  return (
    <form action={formAction}>
      <input name="title" required />
      <textarea name="body" required />
      {state.error ? <p role="alert">{state.error}</p> : null}
      <button type="submit" disabled={isPending}>
        {isPending ? "Publishing..." : "Publish"}
      </button>
    </form>
  )
}

Quando um hook comanda a ação, a assinatura da função ganha um primeiro parâmetro prevState antes de FormData. Retornar um objeto em vez de lançar uma exceção permite que o componente renderize a validação inline sem um error boundary:

app/posts/actions.tstypescript
"use server"

import { revalidateTag } from "next/cache"
import { PostService } from "@/lib/services/post-service"

type FormState = { error: string }

export async function createPost(
  prevState: FormState,
  formData: FormData,
): Promise<FormState> {
  const title = formData.get("title") as string

  // Validate on the server; client validation is never trustworthy alone
  if (!title || title.length < 3) {
    return { error: "Title must be at least 3 characters." }
  }

  await PostService.create({ title })

  // Invalidate by tag rather than path for finer-grained control
  revalidateTag("posts")
  return { error: "" }
}

A referência de useActionState no react.dev cobre padrões avançados, como suporte a permalink para envios anteriores à hidratação. Retornar um estado estruturado mantém o caminho de sucesso e o caminho de erro em um único lugar previsível.

revalidatePath vs revalidateTag: escolhendo a invalidação certa

Uma mutação é apenas metade do trabalho; a UI precisa refleti-la. O Next.js expõe duas funções de invalidação em next/cache, e escolher a correta é um critério frequente de diferenciação em entrevistas.

| Função | Invalida | Melhor quando | |----------|-------------|-----------| | revalidatePath("/posts") | Toda entrada em cache de uma rota | A mutação afeta uma página ou layout específico | | revalidateTag("posts") | Todo fetch em cache rotulado com essa tag | Os mesmos dados aparecem em várias rotas |

revalidatePath é abrangente e centrado em rotas: limpa o cache de uma URL e de sua árvore de layouts. revalidateTag é granular e centrado nos dados: qualquer fetch ou função em cache rotulada com a string é purgado onde quer que esteja. Tags escalam melhor em aplicações grandes porque uma única mutação pode atualizar uma barra lateral, uma listagem e uma página de detalhe em uma só chamada. Ambas as funções marcam os dados como obsoletos em vez de refazer a busca imediatamente, de modo que a próxima requisição regenera o conteúdo.

Com os Cache Components do Next.js 16, a rotulagem se integra à diretiva "use cache" por meio de cacheTag, o que muda a forma de raciocinar sobre cache em toda a aplicação. Essa interação é detalhada no guia de Cache Components do Next.js 16 da SharpSkill.

Pronto para mandar bem nas entrevistas de React / Next.js?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Atualizações otimistas de UI com useOptimistic

As idas e voltas pela rede adicionam latência perceptível. O hook useOptimistic renderiza o resultado esperado instantaneamente e depois reconcilia com a resposta do servidor assim que a ação é resolvida. Ele recebe o estado atual e um reducer que produz a versão otimista.

app/posts/like-button.tsxtsx
"use client"

import { useOptimistic } from "react"
import { likePost } from "./actions"

export function LikeButton({ postId, likes }: { postId: string; likes: number }) {
  // optimisticLikes updates before the server confirms
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (current) => current + 1,
  )

  async function handleLike() {
    addOptimisticLike(null) // update the UI immediately
    await likePost(postId)  // then run the real mutation
  }

  return (
    <form action={handleLike}>
      <button type="submit">Like ({optimisticLikes})</button>
    </form>
  )
}

Se a ação falhar, o React automaticamente reverte o valor otimista para o estado confirmado pelo servidor, portanto nenhum rollback manual é necessário. A referência de useOptimistic no react.dev detalha como o ciclo de reconciliação interage com as transitions. É esse padrão que faz os formulários com Server Actions parecerem tão responsivos quanto uma SPA totalmente renderizada no cliente.

Vinculando argumentos e chamando ações a partir de manipuladores de eventos

Nem toda mutação nasce do envio de um formulário. Excluir uma linha, alternar uma configuração ou reordenar uma lista frequentemente parte de um clique de botão ou de outro evento. Uma Server Action é uma referência de função comum no cliente, então pode ser chamada de qualquer manipulador, desde que a chamada rode dentro de uma transition para manter a UI responsiva.

Argumentos adicionais que não são campos do formulário são anexados com bind, que produz uma nova ação com esses valores adicionados no início. Essa é a forma idiomática de passar um ID junto com o FormData enviado.

app/posts/post-actions.tsxtsx
"use client"

import { useTransition } from "react"
import { deletePost, updatePost } from "./actions"

export function PostActions({ postId }: { postId: string }) {
  const [isPending, startTransition] = useTransition()

  // Bind the id so the action receives it before FormData
  const updateWithId = updatePost.bind(null, postId)

  return (
    <div>
      <form action={updateWithId}>
        <input name="title" />
        <button type="submit">Save</button>
      </form>

      {/* Event-handler invocation wrapped in a transition */}
      <button
        onClick={() => startTransition(() => deletePost(postId))}
        disabled={isPending}
      >
        {isPending ? "Deleting..." : "Delete"}
      </button>
    </div>
  )
}

Argumentos vinculados são criptografados como qualquer outro valor capturado por closure, então o postId não é adulterado em trânsito, embora a ação ainda precise confirmar que quem a chamou tem permissão para agir sobre aquele registro. Envolver a chamada direta em startTransition permite que o React mantenha a interface interativa e exponha o estado de pendência sem precisar de um formulário.

Segurança de Server Actions: trate toda ação como uma API pública

O equívoco mais perigoso é achar que uma Server Action é privada por estar escrita ao lado do código do servidor. Na prática, assim que uma ação é utilizada, o Next.js gera um endpoint POST público que qualquer pessoa pode chamar com uma requisição forjada. O framework fornece IDs de ação seguros e elimina ações não utilizadas durante o next build, mas não autoriza quem faz a chamada.

Autorize dentro de cada ação

Server Actions são endpoints HTTP acessíveis. Verificações de sessão em um layout ou middleware não as protegem, porque um atacante pode enviar um POST diretamente para a ação. Verifique a sessão e as permissões de quem chama dentro do corpo da ação antes de tocar em qualquer dado.

app/admin/actions.tstypescript
"use server"

import { auth } from "@/lib/auth"
import { headers } from "next/headers"
import { UserService } from "@/lib/services/user-service"

export async function deleteUser(userId: string) {
  // Authenticate on every invocation, not in a wrapper component
  const session = await auth.api.getSession({ headers: await headers() })
  if (!session || session.user.role !== "admin") {
    throw new Error("Unauthorized")
  }

  await UserService.delete(userId)
}

Mais duas regras importam. Valores capturados por closure em uma ação inline são criptografados antes de serem enviados ao cliente e descriptografados no servidor, então um token capturado não vaza em texto puro, mas depender disso é frágil: mantenha segredos totalmente fora das closures. E qualquer argumento vindo do cliente é uma entrada não confiável que precisa ser validada, exatamente como o corpo de uma requisição REST. O guia de segurança de dados do Next.js formaliza o modelo de taint por trás dessas garantias. Para praticar de forma estruturada esses trade-offs, o módulo de entrevista sobre Server Actions do Next.js da SharpSkill treina exatamente as perguntas que as bancas de contratação fazem.

Perguntas de entrevista sobre Server Actions no Next.js 16

Essas perguntas aparecem repetidamente nos processos seletivos sênior de React e full-stack em 2026.

Por que uma Server Action só pode aceitar e retornar valores serializáveis? Porque argumentos e resultados atravessam a fronteira de rede entre cliente e servidor como payloads codificados. Funções, instâncias de classe e nós do DOM não podem ser serializados, então passá-los lança um erro. FormData, objetos simples, arrays e primitivos são seguros.

Como o aprimoramento progressivo funciona com Server Actions? Quando uma ação é passada diretamente para a prop action de um formulário, o navegador envia o formulário nativamente por meio de uma requisição POST mesmo antes de o React hidratar. O Next.js intercepta e executa a ação no servidor, então o formulário funciona sem JavaScript no cliente e evolui para uma experiência conduzida pelo cliente após a hidratação.

Qual é a diferença entre lançar e retornar um erro em uma ação? Lançar propaga o erro até o error boundary mais próximo e é adequado para falhas inesperadas. Retornar um objeto de estado estruturado por meio de useActionState é adequado para erros de validação esperados, que devem ser renderizados inline sem desmontar o formulário.

Chamar revalidatePath refaz a busca dos dados imediatamente? Não. Ele marca as entradas em cache como obsoletas para que a próxima requisição as regenere. A resposta atual não é afetada, e é por isso que um redirecionamento após uma mutação chega a um conteúdo recém-renderizado.

Para o contexto de renderização mais amplo em que essas mutações rodam, a análise React 19 Server Components em produção combina naturalmente com o conhecimento sobre Server Actions.

Pronto para mandar bem nas entrevistas de React / Next.js?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Conclusão

As Server Actions do Next.js 16 consolidam mutações, revalidação e aprimoramento progressivo em uma única primitiva server-first. Os principais pontos:

  • Marque mutações com "use server" e passe-as para a prop action de um formulário para obter tratamento de rede e aprimoramento progressivo de graça.
  • Conduza os estados de pendência e validação com useActionState, retornando um objeto estruturado para erros esperados em vez de lançar exceções.
  • Use revalidatePath quando uma rota muda e revalidateTag quando os mesmos dados se espalham por várias rotas.
  • Use useOptimistic para renderizar o resultado esperado instantaneamente e deixe o React reconciliar ou reverter automaticamente.
  • Autorize e valide dentro do corpo de cada ação, porque cada ação utilizada é um endpoint HTTP público que o middleware sozinho não protege.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#next.js
#server-actions
#react
#mutations
#revalidation
#interview

Compartilhar

Artigos relacionados