// app/actions/update-profile.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
const profileSchema = z.object({
name: z.string().min(2, 'Numele e prea scurt'),
bio: z.string().max(200, 'Bio-ul e prea lung').optional(),
});
export async function updateProfileAction(prevState: any, formData: FormData) {
const rawData = Object.fromEntries(formData.entries());
const validated = profileSchema.safeParse(rawData);
if (!validated.success) {
return { success: false, errors: validated.error.flatten().fieldErrors };
}
await db.user.update({
where: { id: 'current-user-id' },
data: validated.data,
});
revalidatePath('/dashboard/profile');
return { success: true, errors: null };
}Salutare tuturor. Am văzut tot mai mulți devși care aruncă Server Actions peste tot doar pentru că sunt mai noi, sau din contră, le evită complet și rămân blocați pe vechiul tipar cu fetch('/api/...').
M-am lovit de dilema asta în timp ce refăceam o aplicație SaaS cu vreo 12k utilizatori activi. Aveam de rezolvat trei cazuri clasice: un formular complex de update profil, upload de imagini și un webhook de la Stripe. După câteva refactoring-uri și ceva dureri de cap, mi-am definit niște reguli clare.
1. Form submit și modificări de stare: Server Actions câștigă clar
Pentru formulare din UI-ul tău, Server Actions sunt genial de comode. Am scos vreo 180 de linii de cod inutil — am scăpat de boilerplate-ul de Axios/React Query, de manual useState pentru erori și de definirea rutelor dedicate.
Integrate cu useActionState și Zod pentru validare, funcționează brici. Revalidarea de cache cu revalidatePath sau revalidateTag se întâmplă direct pe server, fără să mai pasezi token-uri JWT în headers pe client la fiecare request.
Trade-off sincer: Dacă vrei să folosești Server Actions pe o aplicație mobilă nativă sau să le expui către un client extern, ai tras lozul necâștigător. Sunt strâns legate de ecosistemul Next.js.
2. File Uploads: Depinde enorm de dimensiune
Am încercat inițial să trec un avatar de 2MB printr-un Server Action. A mers ok. Când un user a încercat să urce un PDF de 15MB, funcția mea serverless din Vercel a crăpat din cauza limitei de payload și a consumului de memorie (Server Actions fac buffer la tot request-ul în memorie înainte să poți procesa fișierul).
- Pentru fișiere mici (avatar, un document text sub 2MB): Server Action e suficient și scrii puțin cod.
- Pentru fișiere mari: API Route e obligatoriu. Flow-ul corect pe care l-am implementat a fost un API Route care doar generează un Pre-signed URL pentru AWS S3 / Cloudflare R2, iar clientul face
PUTdirect din browser în bucket. Serverul tău nici nu atinge fișierul.
3. Webhook-uri terțe (Stripe, SendGrid, GitHub): Exclusiv API Routes
Aici nu există discuție. Server Actions folosesc un protocol intern bazat pe request-uri POST speciale cu headere specifice Next.js pe care servicii precum Stripe sau SendGrid nu le pot replica.
Pentru orice apel vin de la un server extern (machine-to-machine), trebuie să folosești un Route Handler clasic (/app/api/webhooks/stripe/route.ts). Ai nevoie de acces la request-ul brut (raw body) pentru a verifica semnătura criptografică de securitate, lucru pe care nu-l poți face curat într-un Server Action.
Regula mea de aur
E simplu: Server Actions pentru orice interacțiune inițiată de utilizator din interfața ta Next.js (mutații, formulare, toggle-uri). API Routes (Route Handlers) pentru webhook-uri terțe, fișiere mari, rute publice de API sau când ai nevoie de streaming/headers personalizate pe răspuns.
Voi cum ați împărțit rutele pe proiectele ultime? Ați trecut complet pe Server Actions la form-uri sau încă preferați React Query + API routes?