webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
Next.js · Lesson 8 of 12

Server Actions and forms

Safe mutations, useActionState, validation and optimistic UI.

Updated

What a Server Action is

A Server Action is an async function marked with 'use server' that you call from the UI (a form, a button), but that runs on the server. Next automatically creates an endpoint and makes the request for you.

You use them for mutations: creating, editing, deleting, saving. They replace the "API route + fetch + loading state" combo for your own forms.

// features/create-post/api/create-post.ts
'use server'

import { updateTag } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createPost(prev: State, formData: FormData) {
  const session = await auth()
  if (!session) return { error: 'Unauthorized' }

  const title = String(formData.get('title') ?? '').trim()
  if (title.length < 3) return { error: 'The title is too short' }

  const post = await db.post.create({ data: { title, authorId: session.userId } })
  updateTag('posts')
  redirect(`/posts/${post.id}`)
}

This app saves your progress exactly like this (entities/progress/api/save-progress.ts).

Security — the most important thing

A Server Action is a public POST endpoint. Anyone can call it directly, without your UI, with whatever data they want.

  • Check authentication and authorization in every action ("are they logged in?" and "are they allowed on this resource?").
  • Validate all input (zod / valibot) — formData can contain anything.
  • Don't return sensitive data.

Ways to call a Server Action

Way Code When
<form action={fn}> receives FormData the default for forms; works even without JS
useActionState(fn, initial) you get [state, action, pending] showing errors / messages / pending
<button formAction={fn}> several actions in one form "Save" vs "Delete"
a direct call in a handler await fn(args) interactions that aren't forms (a like, autosave)

A form with useActionState

'use client'
import { useActionState } from 'react'

export function PostForm() {
  const [state, action, pending] = useActionState(createPost, { error: null })
  return (
    <form action={action}>
      <input name="title" required />
      <p aria-live="polite">{state.error}</p>
      <button disabled={pending}>{pending ? 'Saving…' : 'Save'}</button>
    </form>
  )
}

With useActionState, the action receives the previous state as its first argument and the FormData as the second.

Progressive enhancement: a form rendered on the server with a Server Action works even if the JS hasn't loaded yet.

The full flow, with Zod validation and values kept after an error, is in Forms with Server Actions.

Optimistic UI

const [optimisticLikes, addLike] = useOptimistic(likes, (state, n: number) => state + n)

<form action={async () => { addLike(1); await like(postId) }}>

The UI changes instantly; if the action fails, React goes back to the real value.

Server Action vs Route Handler

Server Action Route Handler
forms and buttons in your app an API consumed by others (mobile, partners)
mutations webhooks (Stripe, a CMS)
called sequentially, one at a time non-HTML responses (RSS, files, public JSON)

Reading data isn't done through Server Actions — that's what Server Components are for.

Summary

  • A Server Action = a 'use server' function called from the UI, run on the server; for mutations.
  • Every action: auth + authorization + validation.
  • <form action> + useActionState for errors and pending; updateTag after a mutation.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.