
Server Actions, Services, and Validation: A Next.js SaaS Pattern
Server actions are the boundary, not the place your business logic should live. Here is the split that keeps both clean.
Convenient is not the same as correct
Server actions in Next.js are convenient enough that it is tempting to put everything in them: validation, auth checks, database queries, business rules, all in one function. It works, right up until you need to reuse that logic, test it, or call it from somewhere else, and you find it is welded to a form submission.
A cleaner pattern keeps the action thin and pushes the real work into a service. The action is a boundary; the service is the logic.
What the action should do
A server action is the web boundary, and it should stay narrow:
- Accept the incoming input, including form data
- Validate it with a schema so bad input never reaches your logic
- Check authentication and the current user
- Call a service with typed arguments
- Return a typed, predictable response
That is the whole job. The moment an action starts making business decisions, it has taken on a responsibility that belongs elsewhere.
What the service should do
The service is where the actual work lives: a typed function that takes plain arguments, talks to the database, applies business rules, and returns a result. It does not know about forms or requests, which is exactly what makes it reusable and testable. Two callers, a server action and a scheduled job, can use the same service without duplicating logic.
Validation belongs at the boundary
Validating input with a schema at the action boundary means everything past that point can trust its inputs. It is the cheapest way to prevent a whole category of bugs, and it pairs naturally with type safety: validate once at the edge, then let types carry the guarantees inward. Done consistently, this is a meaningful part of keeping a SaaS stable rather than surprising.
Why this pattern compounds
This separation only pays off if it is consistent, which is why it belongs next to a clear Next.js SaaS folder structure. It is also what makes data access safe to reason about, since services are where row-level concerns from Supabase RLS for SaaS get applied. And because the pattern is predictable, it is exactly the kind of rule an AGENTS.md guardrail file can hold an AI agent to.
Where aSaaSin fits
aSaaSin is built on this pattern throughout: thin actions at the boundary, typed services for logic, and schema validation at the edge. The consistency means new features have an obvious shape, whether you or an agent writes them.
To see the pattern in a real codebase, see pricing or explore the docs.