Server Actions and Forms

Intermediate
13 min

Server Actions and Forms

Before Server Actions, every mutation meant an API route, a fetch call, loading flags and error handling by hand. A Server Action is an async function that runs on the server and can be called directly from a form or a component; Next.js generates the endpoint for you. After this lesson you will be able to define actions, wire them to forms, refresh the UI after a write, and call actions from Client Components safely.

Defining an Action

The "use server" directive marks a function, or every export of a file, as a Server Action:

typescript
// app/todos/actions.ts "use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { db } from "@/lib/db"; export async function createTodo(formData: FormData) { const title = String(formData.get("title") ?? "").trim(); if (!title) return; await db.todo.create({ data: { title } }); revalidatePath("/todos"); } export async function deleteTodo(id: string) { await db.todo.delete({ where: { id } }); redirect("/todos"); }

A dedicated actions file can be imported by both Server and Client Components, and the directive at the top applies to every export. An action can also be declared inline in a Server Component by placing "use server" as the first line of the function body.

Wiring a Form

Pass the action to the action attribute of a <form>, as in the sample at the top of this lesson. On submit the browser POSTs to the current URL; Next.js routes the request to the action with a FormData object and, because the action called revalidatePath, responds with the updated page in the same round trip. No onSubmit, no fetch, no manual state.

Because it is a plain HTML form, submission also works before JavaScript has loaded: progressive enhancement for free.

Passing Extra Arguments

Actions accept ordinary parameters when called from code. With forms, add hidden inputs or bind arguments ahead of time:

tsx
// components/DeleteButton.tsx import { deleteTodo } from "@/app/todos/actions"; export function DeleteButton({ id }: { id: string }) { const deleteWithId = deleteTodo.bind(null, id); return ( <form action={deleteWithId}> <button type="submit">Delete</button> </form> ); }

Bound arguments are encrypted before being embedded in the page, but you should still authorise the operation on the server.

Calling Actions From Client Components

A Client Component cannot define an action, but it can import one from a "use server" file and call it like any async function:

tsx
"use client"; import { useTransition } from "react"; import { toggleTodo } from "@/app/todos/actions"; export function TodoCheckbox({ id, done }: { id: string; done: boolean }) { const [pending, startTransition] = useTransition(); return ( <input type="checkbox" checked={done} disabled={pending} onChange={() => startTransition(() => toggleTodo(id))} /> ); }

useTransition provides a pending flag while the request is in flight.

Security Model

Every exported action is a public POST endpoint, callable by anyone who discovers its id. Treat it like an API route:

  • Authenticate and authorise inside the action; never rely on the page that rendered the form.
  • Validate all inputs; FormData values are untrusted.
  • Next.js compares Origin and Host headers to block cross-site submissions; set serverActions.allowedOrigins in next.config.ts when a proxy changes the host.

Common mistakes

  • Wrapping redirect() in try/catch inside an action; it throws by design and the catch swallows it.
  • Forgetting revalidatePath or revalidateTag, so the form submits but the list on screen does not change.
Quick Quiz
Question 1 of 3

What does the `"use server"` directive at the top of a file do?

Key Takeaways

  • A Server Action is an async server function marked with "use server", callable from forms and components.
  • Pass it to <form action={...}> to get progressive enhancement and automatic FormData handling.
  • Call revalidatePath or revalidateTag after writes so the UI updates in the same round trip.
  • Use .bind or hidden inputs for extra arguments and useTransition in event handlers.
  • Actions are public endpoints: authenticate, authorise and validate inside each one.

Next lesson: Form State, Validation and Optimistic UI — return validation errors, show pending states and update the UI before the server responds.

Server Actions and Forms - Next.js | CodeYourCraft | CodeYourCraft