Server Actions
By Flavio Copes
Learn how React Server Actions work using the 'use server' directive to define server-only functions you can call from a client component form action.
Server actions are functions that run only on the server, but that you can call from client components. You mark them with the 'use server' directive, and React handles the network call for you.
Why do we need them? Think about a form submission. Traditionally you’d create an API endpoint, write a fetch() call, serialize the data, handle the response. Server actions remove all of that. You write a function, pass it to a form, done. The framework (like Next.js) creates the endpoint behind the scenes.
They’re great for anything that touches the server: saving to a database, sending an email, reading files.
How to define a server action
Server actions are defined in a separate .ts file marked with the 'use server' directive at the top.
This tells React that what is in that file can only run on the server:
//actions.ts
'use server'
export async function createInvoice(formData: FormData) {
//we are on the server, we can directly
//do something with the form data
const fullName = formData.get('fullName')
//...save it to the database
}
Two things to notice. The function must be async, that’s a requirement for server actions. And we export it, so client components can import it.
When the action is triggered by a form, it receives a FormData object. You read the fields with formData.get(), passing the name of the input.
How to call it from a client component
In a client component, import the action and pass it to the form’s action attribute:
'use client'
import { createInvoice } from './actions'
export const InvoiceForm = () => {
return (
<div>
<form action={createInvoice}>
<input
type='text'
name='fullName'
/>
<button type='submit'>Submit</button>
</form>
</div>
)
}
When the user submits, React calls createInvoice() on the server with the form data. No fetch(), no API route, no onSubmit handler.
Watch out for the name attribute
A pitfall I’ve hit: formData.get('fullName') returns null if the input has no matching name attribute.
It’s easy to build the form, wire up the action, and forget the name on one input. The action runs fine, but that field is always null. If a value goes missing on the server, check the input’s name first.