Skip to content
C.W.K.
Stream
Lesson 01 of 08 · published

What Are Server Actions?

~20 min · Server Action, use server, mutations

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Server Actions = mutation endpoints, declared inline

A Server Action is an async function that runs on the server but is callable from a Client Component. The framework wires it as a unique POST endpoint behind the scenes — you call it like a function, not like an API.

Two ways to declare

ApproachHow
File-level'use server' at the top of the file. Every exported function in that file is an action.
InlineInside a Server Component, define the function and put 'use server' at the top of its body.

What they replace

For internal CRUD, you no longer need /api/…/route.ts + fetch() + JSON parsing on both sides. Server Actions are POST endpoints with built-in CSRF protection, encrypted action IDs, and automatic dead-code elimination of unused actions.

What they don't replace

Public APIs (third parties call them), webhooks, integrations, and anything that needs to expose a stable URL still belong in Route Handlers (covered in the next track).

Keep the mutation transaction legible in one place: parse input, read the session, authorize the resource, write, and invalidate affected reads. Group actions by feature so multiple forms can reuse the same domain change without building one unrelated global action file.

A function-shaped call is still a network POST. It can be delayed, retried, duplicated, or invoked outside your UI. Payments and orders need idempotency keys or database constraints just as a conventional endpoint would. Implement one internal mutation both as Route Handler plus fetch and as a Server Action. Compare boilerplate and network surfaces while ensuring validation, authorization, duplicate submission behavior, and cache updates remain explicit.

Code

File-level actions module·ts
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;
  await db.post.create({ data: { title, content } });
  revalidatePath('/posts');
}
Inline action inside a Server Component·tsx
// app/page.tsx
export default function Page() {
  async function handleSubmit(formData: FormData) {
    'use server';
    await db.post.create({ data: { title: formData.get('title') as string } });
  }
  return (
    <form action={handleSubmit}>
      <input name="title" required />
      <button>Create</button>
    </form>
  );
}

External links

Exercise

Replace one of your project's POST /api/…/route.ts CRUD endpoints with a Server Action. Show that the form still works (and that the URL surface for that endpoint disappears).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.