`<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.
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.