Stop Overcomplicating Server Actions
Cover photo by Bench Accounting on Unsplash
So, you’ve heard that Server Actions replace your entire API layer. People talk about them like they are a magic wand for state management, implying you should just dump all your business logic inside a form action and call it a day. It sounds great, but it usually leads to a messy pile of coupled code once your app grows past a simple counter example.
Are they really just magic?
Server Actions are just RPC calls. That is it. They are useful for reducing boilerplate, but they aren’t a replacement for a well-thought-out service layer. You should still keep your heavy lifting in separate files. Don’t write your SQL queries inside your component files just because you can.
“But why write an extra function when I can just put it right in the server action?” You’re right, for small prototypes, speed is king. I’ll admit that when I’m spinning up a side project, I often skip the service layer entirely. It’s a trade-off. Once you have two or more actions hitting the same database table, though, you will regret not abstracting that logic.
Keep it clean
Here is how I structure a basic mutation. Keep the action light and delegate the work.
'use server';import { updateUserEmail } from '@/services/user';
export async function updateEmailAction(prevState, formData) { const email = formData.get('email'); // Basic validation if (!email) return { error: 'Missing email' };
return await updateUserEmail(email);}See? The action handles the request context and the service handles the database stuff. (If you’re using something like Drizzle, your service file stays incredibly clean.) This setup makes it trivial to test your business logic without ever firing up a browser or a mock HTTP server.
The limits of this approach
I struggle with the error handling story in Next.js (it still feels a bit clunky compared to a dedicated API route). Sometimes, standard form handling isn’t enough when you need complex optimistic UI updates. In those cases, I still reach for TanStack Query. It’s not an either-or situation.
Are Server Actions the future of mutations? Maybe. They are certainly convenient, but don’t let the simplicity trick you into abandoning good software architecture. Use them to bridge your UI to your services, but don’t let them become the place where your business logic lives and dies. How are you balancing your service layers in your current project?