NextjsServer Actions

Server Actions

A Server Action is an asynchronous function marked with the 'use server' directive that runs on the server but can be called directly from your components as if it were a normal JavaScript function — no manual API route, no fetch call, no request/response plumbing. If you haven't seen the 'use server' directive yet, the "The 'use server' Directive" page covers it in isolation; this page focuses on what Server Actions let you build.
Note
Under the hood, Next.js still sends a network request when a Server Action runs — it isn't magic, it's a real POST to the server. What changes is who writes the plumbing: Next.js generates and wires up a secure, non-guessable endpoint for you, so you never hand-write the route, the serialization, or the client-side call.
The old way: a Route Handler plus a fetch call
Before Server Actions, saving a piece of data from a Client Component meant writing at least two things that had to stay in sync: an API route on the server, and a fetch call on the client that knew that route's URL, method, and body shape.

app/api/todos/route.ts — the old way, part 1

TS
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const body = await request.json()

  // ...save body.title to the database...

  return NextResponse.json({ success: true })
}

components/AddTodo.tsx — the old way, part 2

TSX
'use client'

import { useState } from 'react'

export default function AddTodo() {
  const [title, setTitle] = useState('')

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()

    await fetch('/api/todos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title }),
    })

    setTitle('')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <button type="submit">Add</button>
    </form>
  )
}

That works, but it's a lot of ceremony for "save this string." You own the URL, the HTTP method, the JSON serialization on both ends, and keeping the client's idea of the request shape in sync with the server's idea of it forever.

The new way: call the function directly
A Server Action collapses all of that into one function. You write it once, mark it with 'use server', and call it from a Client Component exactly like a local function — Next.js handles serializing the arguments, sending the request, and returning the result.

app/actions/todos.ts

TS
'use server'

export async function addTodo(title: string) {
  // ...save title to the database...

  return { success: true }
}

components/AddTodo.tsx

TSX
'use client'

import { useState } from 'react'
import { addTodo } from '@/app/actions/todos'

export default function AddTodo() {
  const [title, setTitle] = useState('')

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    await addTodo(title)
    setTitle('')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <button type="submit">Add</button>
    </form>
  )
}
No route file, no URL to remember, no manual JSON encoding. The function signature is the API contract, and TypeScript checks it end to end.
Inline in a Server Component, or in a shared file
Server Actions can live in two places, and each suits a different situation. For a form that only appears once, define the action inline, right inside the Server Component that renders the form — this keeps the mutation next to the UI that triggers it and avoids a separate file for one-off logic.

app/todos/page.tsx — inline action in a Server Component

TSX
export default function TodosPage() {
  async function addTodo(formData: FormData) {
    'use server'

    const title = formData.get('title')
    // ...save title to the database...
  }

  return (
    <form action={addTodo}>
      <input name="title" />
      <button type="submit">Add</button>
    </form>
  )
}
When the same mutation needs to be called from several different Client Components — or from both a form and a button click handler — pull it out into its own file with a module-level 'use server' directive at the top, as in the app/actions/todos.ts example above. Every export in that file becomes an importable Server Action.

Placement

Best for

Inline, inside a Server Component

A single form defined and used in one place; keeps logic colocated

Separate 'use server' file

Reused across multiple Client Components, or shared by a form and a button

Tip
You don't have to choose upfront. Start inline for a one-off form, and move the function into a shared actions file the moment a second component needs to call it.
  • A Server Action is an async function marked 'use server' that runs on the server but is called like a normal function from your components.

  • It replaces the old pattern of hand-writing a Route Handler plus a matching fetch call for simple mutations.

  • Next.js generates a secure, non-guessable endpoint for each action automatically — you never design or expose that API surface yourself.

  • Define inline inside a Server Component for a form used in one place; move to a separate 'use server' file to reuse across Client Components.