Forms With Server Actions

`<form action={serverAction}>` — No Client JS Needed

Forms With Server Actions

A plain HTML `<form>` can call a Server Action directly. The result is progressive enhancement out of the box — works without JS, faster with it.

4 min read Level 2/5 #nextjs#forms#server-actions
What you'll learn
  • Wire an action onto a `<form>` element
  • Read fields from the `FormData` argument
  • Trigger revalidation after a successful mutate

The classic pattern of “controlled inputs + useState + fetch + JSON” is replaced with one line: <form action={create}>. The form posts to your Server Action, which receives a FormData object.

Inline Server Action in a Server Component

For small mutations, you can define the action inline.

// app/notes/page.tsx
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export default function Page() {
  async function create(form: FormData) {
    'use server'
    const title = String(form.get('title') ?? '')
    await db.notes.insert({ title })
    revalidatePath('/notes')
  }

  return (
    <form action={create}>
      <input name="title" placeholder="Note title" />
      <button>Add</button>
    </form>
  )
}

The 'use server' directive at the top of the function body marks it as an action even though it lives inside a Server Component.

Reading Form Data

Field values come from FormData.get(name). Names match the name attribute on each input.

async function create(form: FormData) {
  'use server'
  const title = String(form.get('title') ?? '')
  const tags = form.getAll('tag') // multi-select / checkbox group
}

Always Revalidate

After mutating data, call revalidatePath or revalidateTag. Without it, the page still shows the old data because the cached fetch has not refreshed.

useActionState →