Ditch API Routes and switch to Server Actions. Get a “boundaryless” development experience with Next.js App Router
“Are you writing the API endpoint definition again?”
Do you ever feel a sense of emptiness at the end of development? Make requests on the front end, write validations on the back end, and go back and forth to the `shared` directory to share types. How much time have you spent in rituals to overcome this "wall"?
In 2024, Next.js' App Router and Server Actions will make this "division" a thing of the past. This time, we will discuss how to get rid of API Routes and get the optimal solution for full stack development. I will share the real-world knowledge I learned while vomiting blood.
1. Why Server Actions are a “game changer”
The essence of Server Actions is that we have abolished the ``communication abstraction'' of API endpoints and returned to the ``extreme abstraction'' of function calls.
The ``client-server bridge'' that has traditionally been handled by REST and gRPC can now be written as a simple asynchronous function. This erases the boundaries between front end and back end.
The true value of type safety
In the case of APIs, tragedies often occur where response type definitions are misaligned and runtime errors occur. However, with Server Actions, just by `import`, the type on the server side is propagated to the front as is.// features/profile/actions.ts
'use server'
import { z } from 'zod'
import { db } from '@/lib/db'
import { auth } from '@/auth'
const schema = z.object({
name: z.string().min(2, "Please enter at least 2 characters"),
})
export async function updateProfile(prevState: any, formData: FormData) {
const session = await auth()
if (!session) throw new Error("Unauthorized")
const validated = schema.safeParse(Object.fromEntries(formData))
if (!validated.success) return { error: validated.error.flatten() }
await db.user.update({
where: { id: session.user.id },
data: { name: validated.data.name }
})
return { success: true }
}
`
In this way, Zod is installed and validation is forced on the server side. Gone are the days of worrying about the return value of `fetch`.
2. The golden ratio of architecture: the 80:15:5 rule
The extreme idea of "making everything Server Actions" will kill performance. This is the composition ratio that I came up with that balances maintainability and speed.
| Category | Ratio | Role |
|---|---|---|
| Server Components | 80% | Data retrieval, caching, static generation |
| Server Actions | 15% | Data update (Mutation), form processing |
| Client Components | 5% | Interactions, Animations |
Why you shouldn't use Server Actions to retrieve data
A common mistake many people make is using Server Actions to retrieve data. Server Actions are issued as POST requests, so browser caching does not work.The golden rule for data acquisition is to directly access the DB using the Server Component. Next.js automatically performs `Request Memoization` (caching of duplicate calls within the same request) here. As a result, even if you hit the DB deep in the component tree, the optimization will actually only be executed once.
3. The unavoidable “Road of Death”: Traps to watch out for
Server Actions are powerful, but they're not magic. Here are some countermeasures for the "hell" that developers always face.
Trap 1: DB connection exhaustion
If you abuse `auth()` or DB queries in Server Actions, each request will consume connections and the DB will become punctured. Especially when performing parallel processing with `Promise.all`, always be aware of the limitations of the connection pool.Trap 2: Complicated debugging
If it's an API, you can just hit it with `cURL`, but since Server Actions are tightly coupled with the client, it's difficult to debug them outside of the network tab of your browser. Error logs must be collected on the server side so that they can be traced using monitoring tools such as Sentry.Trap 3: Cache inconsistency
If you do not manage `revalidatePath` and `revalidateTag` properly, the UI will forever display an outdated state. Designing this "cache update timing" is the biggest difficulty in App Router.Conclusion: Have the courage to abandon APIs
Ditching API Routes isn't just about reducing code. Eliminate the complexity of "communications" from the application's concerns.
- Is an API definition file unnecessary? → Not required.
- Is dual validation management necessary? → Zod on the server side is enough.
- Do I need client fetch logic? → Resolved easily with `useActionState`.
Reduce code. Ditch complexity. And focus on the business logic itself.
Server Actions is the sharpest knife for that purpose. Once you master it, your development speed will literally change by an order of magnitude.