Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// app/actions.ts
"use server";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
const body = formData.get("body") as string;
await db.insert("posts", { title, body, createdAt: new Date() });
revalidatePath("/posts");
}// app/posts/NewPostForm.tsx
"use client";
import { createPost } from "../actions";
export default function NewPostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="Título" required />
<textarea name="body" placeholder="Escreva seu post..." required />
<button type="submit">Publicar</button>
</form>
);
}Quando usar isso: Use Server Actions para qualquer mutação de dados (criar, atualizar, excluir) que precise ser executada no servidor -- gravações em banco de dados, uploads de arquivos, envio de e-mails, chamadas a APIs de terceiros com segredos.
// Um aplicativo de tarefas completo com server actions, validação e tratamento de erros
// app/actions.ts
"use server";
import { z } from "zod";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
const TodoSchema = z.object({
text: z.string().min(1, "O texto da tarefa é obrigatório").max(200, "Muito longo"),
});
export type ActionResult = {
success: boolean;
error?: string;
};
export async function addTodo(_prev: ActionResult, formData: FormData): Promise<ActionResult> {
const parsed = TodoSchema.safeParse({ text: formData.get("text") });
if (!parsed.success) {
return { success: false, error: parsed.error.errors[0].message };
}
try {
await db.insert("todos", {
text: parsed.data.text,
completed: false,
createdAt: new Date(),
});
revalidatePath("/todos");
return { success: true };
} catch {
return { success: false, error: "Falha ao salvar a tarefa" };
}
}
export async function toggleTodo(id: string) {
const todo = await db.findById("todos", id);
if (!todo) throw new Error("Não encontrado");
await db.update("todos", id, { completed: !todo.completed });
revalidatePath("/todos");
}
export async function deleteTodo(id: string) {
await db.delete("todos", id);
revalidatePath("/todos");
}// app/todos/TodoApp.tsx
"use client";
import { useActionState, useOptimistic, useTransition } from "react";
import { addTodo, toggleTodo, deleteTodo, type ActionResult } from "../actions";
type Todo = { id: string; text: string; completed: boolean };
export default function TodoApp({ todos }: { todos: Todo[] }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newText: string) => [
...state,
{ id: "optimistic", text: newText, completed: false },
]
);
const [state, formAction, isPending] = useActionState(
async (prev: ActionResult, formData: FormData) => {
const text = formData.get("text") as string;
addOptimisticTodo(text);
return addTodo(prev, formData);
},
{ success: true }
);
return (
<div>
<h1>Tarefas</h1>
<form action={formAction}>
<input name="text" placeholder="O que precisa ser feito?" disabled={isPending} />
<button type="submit" disabled={isPending}>
{isPending ? "Adicionando..." : "Adicionar"}
</button>
{state.error && <p className="text-red-500">{state.error}</p>}
</form>
<ul>
{optimisticTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
</div>
);
}
function TodoItem({ todo }: { todo: Todo }) {
const [isPending, startTransition] = useTransition();
return (
<li className={isPending ? "opacity-50" : ""}>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => startTransition(() => toggleTodo(todo.id))}
/>
<span className={todo.completed ? "line-through" : ""}>{todo.text}</span>
</label>
<button onClick={() => startTransition(() => deleteTodo(todo.id))}>
Excluir
</button>
</li>
);
}O que isso demonstra:
"use server" exportando múltiplas server actionsuseActionState para gerenciamento de estado de formuláriostartTransition"use server" informa ao bundler para criar um endpoint de rede para essa função. O cliente recebe um ID de referência, não o corpo da função.<form action={}> onde o React coleta automaticamente FormData, ou (2) chamadas diretamente como await deleteItem(id) dentro de um startTransition.revalidatePath ou revalidateTag aciona a re-renderização de Server Components afetados após a conclusão da action.Inline server actions em Server Components:
// A action é definida inline e fecha sobre dados do lado do servidor
export default async function LikePage() {
let likes = await db.getLikes();
async function addLike() {
"use server";
await db.incrementLikes();
}
return (
<div>
<p>Likes: {likes}</p>
<form action={addLike}>
<button type="submit">Curtir</button>
</form>
</div>
);
}Chamando server actions fora de formulários:
"use client";
import { useTransition } from "react";
import { deleteItem } from "./actions";
function DeleteButton({ id }: { id: string }) {
const [isPending, startTransition] = useTransition();
return (
<button
disabled={isPending}
onClick={() => startTransition(async () => {
await deleteItem(id);
})}
>
{isPending ? "Excluindo..." : "Excluir"}
</button>
);
}Vinculando argumentos extras com .bind:
// Aplica parcialmente o id para que o formulário envie apenas FormData
import { updateItem } from "./actions";
function EditForm({ id }: { id: string }) {
const updateWithId = updateItem.bind(null, id);
return (
<form action={updateWithId}>
<input name="name" />
<button type="submit">Salvar</button>
</form>
);
}useActionState seguem a assinatura (prevState: T, formData: FormData) => Promise<T>.(id: string, formData: FormData) => Promise<void>.ActionResult (retornando { success, error? }) para tratamento de erros type-safe em vez de lançar exceções."use server" exige que a função seja async. Correção: Sempre declare server actions com async function.useOptimistic para atualizar a UI imediatamente enquanto as actions processam em ordem.cookies() ou headers() de next/headers dentro do corpo da action.| Abordagem | Quando escolher |
|---|---|
| Server Actions | Mutações de dados padrão em React 19 com suporte do framework |
| API Routes | Quando você precisa de endpoints REST consumidos por clientes não-React |
| tRPC | Segurança de tipo ponta a ponta sem convenções específicas do framework |
| Mutações GraphQL | Grafos de dados complexos com múltiplos clientes |
| Fetch do lado do cliente + API | Quando você precisa de controle total sobre o método HTTP, cabeçalhos, cache |
"use server" marca Funções de Servidor (Server Actions), não Server Components."use client" para desativar Server Components."use server" é exclusivamente para funções que rodam no servidor mas são chamadas de código cliente (formulários, manipuladores de eventos)."use server" no topo de um arquivo de componente transformará cada export em uma Server Action, não em um Server Component - e resultará em erro porque componentes não são actions válidas.Sim. Server actions podem ser chamadas diretamente de manipuladores de eventos envolvidos em startTransition:
const [isPending, startTransition] = useTransition();
<button onClick={() => startTransition(async () => {
await deleteItem(id);
})}>
Excluir
</button>"use server";
import { z } from "zod";
const Schema = z.object({
text: z.string().min(1).max(200),
});
export async function addItem(_prev: Result, formData: FormData) {
const parsed = Schema.safeParse({ text: formData.get("text") });
if (!parsed.success) {
return { success: false, error: parsed.error.errors[0].message };
}
// prosseguir com dados válidos
}Use .bind() para aplicar parcialmente argumentos para que o formulário envie apenas FormData:
const updateWithId = updateItem.bind(null, id);
<form action={updateWithId}>
<input name="name" />
<button type="submit">Salvar</button>
</form>revalidatePath("/path") ou revalidateTag("tag") dentro da server actionuseOptimistic para atualizar a UI imediatamente enquanto as actions processam em ordem"use server" são serializáveis através da fronteira servidor-cliente"use server" dentro do corpo da função; elas fecham sobre variáveis do lado do servidor no momento da renderização"use server" no topo do arquivo e exportam múltiplas actionstype ActionResult = { success: boolean; error?: string };
// Assinatura: (prevState: T, formData: FormData) => Promise<T>
export async function myAction(
_prev: ActionResult,
formData: FormData
): Promise<ActionResult> {
return { success: true };
}(id: string, formData: FormData) => Promise<void>.bind(null, id), a assinatura da função resultante é (formData: FormData) => Promise<void>cookies() ou headers() de next/headers dentro do corpo da action<form>Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥