Server Actions in production: what we learned
After a year of shipping Next.js Server Actions in client work, here is where they earn their keep and where a real API endpoint is still the right answer.
Key takeaways
- Server Actions replace form-handling glue, not the API layer.
- Anything a third party calls, or that needs a versioned contract, still wants an endpoint.
- Progressive enhancement and pending states coming free is the real productivity win.
When should you use a Server Action instead of an API route?
Use a Server Action when a mutation belongs to one piece of your own UI and nothing outside the application needs to call it. Use an API route when the operation needs a stable versioned contract, is called by a third party, or is invoked from several unrelated places.
What they removed from our codebases
Server Actions removed an entire category of code from our applications: the API route that exists only to be called by one form, along with its request schema, its fetch wrapper and its two error paths.
Where they shine is mutations tied to a specific piece of UI. Progressive enhancement comes free, the pending state is built in, and revalidation is one call rather than a cache-invalidation dance.
Where they do not fit
Anything a third party needs to call. Anything that needs a stable versioned contract. Anything where the same operation is invoked from several unrelated places. Those still want a real API endpoint, and pretending otherwise produces a duplicated action per caller.
The mistake we made early was treating them as a replacement for the API layer rather than a replacement for form-handling glue. They are the latter.
Authorise inside the action
An action is reachable from the browser like any other endpoint. The component that renders the form is not a security boundary.
Our house rule is that every action begins with the same three lines: resolve the session, assert the permission, parse the input with a schema. If those are not the first statements in the function, the review does not pass.
Server Actions are a better form handler, not a smaller API. Teams that adopt them as the latter end up rebuilding the API by accident.
Keep actions thin and testable
The pattern that has held up best for us is an action that does four things and nothing else: authorise, validate, call a domain function, revalidate. The business logic lives in the domain function, which is a plain module with no framework dependency.
That split matters for testing. A domain function can be unit tested without a request, a session or a rendering context, which is where most of the value in testing this layer sits. Actions themselves get a small number of integration tests covering authorisation and validation failures.
It also makes the eventual API endpoint cheap. When an operation does turn out to need a public contract — and some always do — the endpoint wraps the same domain function, and the action stays as the UI path. Nothing is rewritten and there is only one implementation of the rule.
FAQFAQ
Frequently asked questions
About the author
Tom builds the front end: rendering strategy, performance budgets and accessible interfaces that hold up under real network conditions. He writes about treating Core Web Vitals as a revenue metric rather than a lint rule.
- Core Web Vitals
- Next.js and React
- Web accessibility
- Frontend architecture