SuperAPI AI Directory

nextjs-server-actions

Frontend & websites

View repository

Updated 10-15 20:31

About this skill

Next.js Server Actions

Actions are server mutations invocable by form/startTransition — not a replacement for GET. App Router architecture: nextjs-architecture. Security (authz, CSRF, IDOR): nextjs-server-actions-security.

When to use

Use Server Action Use Route Handler
Mutation of the app itself (form, button) Public API, mobile, third parties
Progressive enhancement with <form> Webhooks, cron, direct upload
Co-located with the UI that mutates Stable REST/JSON contract for external clients

Do not do read fetches in an action. Prefer Server Components / loaders.

Organization

features/<domínio>/
  actions.ts          # "use server" — mutações do domínio
  schemas.ts          # Zod (ou equivalente)
  components/         # forms client ou server
  • "use server" in the top of the file of importable mutations, or inline in Server Components.
  • One action = one business intention (createOrder, archivePost) — not a "controller that does everything".
  • Extract I/O (DB, mail) into services; the action orchestrates validate → authz → mutate → revalidate.

Non-negotiable rules

  • Input validated with schema on the server (FormData or object). Client types are not a guarantee.
  • Authn/authz inside the action (see security skill).
  • Serializable return: { ok: true, data } / { ok: false, code, message } — or redirect/notFound.
  • After write: revalidatePath / revalidateTag / updateTag what became stale.
  • No secrets, Prisma client or Node modules in the Client Components bundle — just import the action.

Forms and progressive enhancement

// Server Component — funciona sem JS
import { createTodo } from "./actions";

export function CreateTodoForm() {
  return (
    <form action={createTodo}>
      <input name="title" required maxLength={200} />
      <button type="submit">Add</button>
    </form>
  );
}
"use client";
import { useActionState } from "react";
import { createTodo } from "./actions";

export function CreateTodoForm() {
  const [state, formAction, pending] = useActionState(createTodo, null);
  return (
    <form action={formAction}>
      <input name="title" required />
      <button disabled={pending}>Add</button>
      {state && !state.ok ? <p>{state.message}</p> : null}
    </form>
  );
}
  • useFormStatus on the child button for pending without lifting state.
  • useActionState for validation/domain errors back to the form.
  • Hidden fields only for non-sensitive data already known to the server (IDs that the action revalidates).

Idiomatic action

"use server";

import { z } from "zod";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

const Schema = z.object({
  title: z.string().trim().min(1).max(200),
});

export async function createTodo(prev: unknown, formData: FormData) {
  const parsed = Schema.safeParse({ title: formData.get("title") });
  if (!parsed.success) {
    return { ok: false as const, code: "validation", message: "Invalid title" };
  }

  // authz + persist...
  revalidatePath("/todos");
  redirect("/todos");
}
  • Signature (prevState, formData) when used with useActionState; otherwise (formData) or explicit args.
  • redirect() throws — do not mix with return afterwards.
  • Extra args: .bind(null, id) on the server; the id is still hostile input — revalidate ownership.

Cache and navigation

  • revalidateTag("todos") when the fetch used the same tag.
  • revalidatePath for the visible route; avoid global revalidate without reason.
  • redirect after create/delete that changes the URL; otherwise return data and let the form/router update.
  • Do not call cookies()/headers() without need — that opts the route into dynamic.

Errors

  • Expected (validation, 409, no permission): object { ok: false, code, message } safe for UI.
  • Unexpected: log + error boundary / digest; do not leak stack to the client.
  • Do not use try/catch to swallow failure of redirect without rethrowing.

Anti-patterns

  • Action that only does fetch to its own Route Handler
  • Validation only on the client (zod in the form without parsing in the action)
  • Return full entities from the DB (hash, tokens, PII)
  • "use server" in a file mixed with client helpers
  • Mutation without revalidate (stale UI)
  • Read/searchParams via action

Flow when implementing

  1. Input schema + result type.
  2. Action: parse → session → policy → persist → revalidate/redirect.
  3. Form server or useActionState on the island.
  4. Test happy path + validation + unauthenticated (security skill).

Completion criteria

  • "use server" isolated and importable without leaking server-only
  • Schema at the action boundary
  • Correct revalidate/redirect after the write
  • Serializable result useful for the form
  • No reads disguised as actions
  • Authz covered (nextjs-server-actions-security)