'use server';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
const ProfileSchema = z.object({
name: z.string().min(2, 'Numele este prea scurt'),
bio: z.string().max(200, 'Bio-ul este prea lung'),
});
export async function updateProfile(prevState: any, formData: FormData) {
const rawData = Object.fromEntries(formData.entries());
const validated = ProfileSchema.safeParse(rawData);
if (!validated.success) {
return { errors: validated.error.flatten().fieldErrors, success: false };
}
// Mutarea efectivă în DB
await db.user.update({
where: { id: 'current-user-id' },
data: validated.data,
});
revalidatePath('/dashboard/profile');
return { success: true, errors: {} };
}M-am lovit recent de dilema asta când refăceam un SaaS B2B cu vreo 12k utilizatori lunari. Echipa voia să treacă absolut totul pe Server Actions pentru că era 'noul standard' din Next.js, dar ne-am dat repede seama că un refactoring orbesc ne băga în bucluc.
După două săptămâni de modificări, am economisit vreo 35% din boilerplate-ul de fetch-uri și state-uri, dar asta doar pentru că am fost selectivi. Hai să le luăm pe rând, din experiența practică, fără povești din documentație.
Form submit și mutații din UI: Server Actions câștigă
Aici Server Actions strălucesc de departe. Înainte, pentru un simplu formular de editat profilul, aveai nevoie de o rută de API (/api/user/update), un fetch pe client, stare de loading, erori prin catch și validare dublă.
Cu Server Actions, definești o funcție async marcata cu 'use server', validezi direct datele (ex: cu Zod) și apelezi revalidatePath(). Totul e direct, tipizat cap-coadă și fără endpoint-uri expuse inutil. La proiectul menționat, am șters peste 20 de fișiere de rute API scurte doar făcând schimbarea asta.
Bonus: funcționează excelent în combinație cu useActionState din React 19 pentru gestionat erorile din UI.
Upload de fișiere: Capcana în care pică mulți
Am văzut mulți dev-i care încearcă să trimită fișiere mari prin Server Actions direct ca parametri sau în Form Data. Nu faceți asta la fișiere mari.
Dacă e vorba de un avatar mic (sub 2MB), un FormData trecut printr-un Server Action e absolut ok. Procesezi rapid pe server și trimiți în storage. Dar dacă ai fișiere video sau PDF-uri mari, Server Action-ul va bloca instanța ta de Node.js/Serverless în timp ce procesează buffer-ul în memorie.
Pentru upload-uri mari, soluția sănătoasă rămâne un Route Handler (/api/upload/presigned-url). Generezi un URL semnat direct pentru S3/R2 pe server, îl trimiți în client, iar browserul face PUT direct în cloud storage. Așa păstrezi serverul aerisit.
Webhooks și consumatori externi: API Routes obligatoriu
Aici nici nu există dezbatere. Un webhook de la Stripe, PayU sau GitHub vine ca un HTTP POST direct de pe serverele lor. Server Actions sunt create strict pentru ciclul de viață al aplicației tale React și necesită headere speciale de Next.js.
Pentru orice înseamnă apel extern, REST API pentru aplicația de mobil sau webhook-uri, folosești Route Handlers (app/api/stripe/route.ts). Doar acolo ai acces curat la Request-ul brut pentru a verifica semnătura HMAC (lucru critic la plăți).
Regula mea de aur
Nu e o competiție, ci o împărțire a sarcinilor:
- Server Actions: Declanșate exclusiv de user din UI-ul tău (form-uri, like-uri, delete, schimbat setări).
- Route Handlers: Webhook-uri, API-uri publice, exporturi mari de date (ex: generat PDF/CSV) și endpoint-uri pentru aplicații mobile.
Voi cum le-ați structurat în proiectele mai mari? Mai folosiți Route Handlers pentru mutații interne sau ați trecut complet pe Server Actions?