// app/actions/update-profile.ts
'use server';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
import { db } from '@/lib/db';
const ProfileSchema = z.object({
name: z.string().min(2, 'Numele este prea scurt'),
email: z.string().email('Email invalid'),
});
export async function updateProfile(prevState: any, formData: FormData) {
const validatedFields = ProfileSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
});
if (!validatedFields.success) {
return { errors: validatedFields.error.flatten().fieldErrors };
}
await db.user.update({
where: { id: 'user_123' },
data: validatedFields.data,
});
revalidatePath('/dashboard/profile');
return { success: true, errors: {} };
}Când App Router a adus Server Actions, mulți am crezut că am scăpat definitiv de /api endpoints. După ce am trecut un SaaS cu 15k utilizatori activi pe Server Actions, am învățat pe pielea mea unde strălucesc și unde îți faci singur viața un iad. Hai să vedem cum alegi corect între cele două fără să rescrii arhitectura peste trei luni.
Formulare și mutații rapide: Câștigă Server Actions
Pentru tot ce înseamnă mutații declanșate direct din UI — un form de profil, un buton de bookmark, adăugat în coș — Server Actions sunt genial de simple. Scapi de boilerplate-ul clasic cu fetch('/api/user'), de stări manuale de loading create cu useState și, cel mai important, ai type-safety nativ direct din baza de date până în componentă.
La proiectul nostru, trecerea formularelor simple pe Server Actions ne-a scăpat de vreo 40KB de JavaScript trimis către client, fiindcă nu mai aveam nevoie de React Query pe paginile alea statice. Fluxul e atomic: trimiți datele, validezi cu Zod pe server, apelezi revalidatePath() și Next.js actualizează UI-ul automat.
File Uploads: Aici e cu cântec
Aici am primit prima palmă. Dacă vrei să faci un upload mic, cum ar fi un avatar de 200KB, un Server Action cu FormData merge fără probleme. Dar când userul vrea să urce un PDF de 30MB sau un fișier video, lucrurile se complică rapid.
Server Actions trec prin limita de payload a serverului de Node.js (pe Vercel ai o limită strictă de 4.5MB per request). Dacă încerci să faci streaming la fișiere mari prin Server Actions, o să-ți blochezi serverless function-ul și dai în timeout-uri scumpe. Pentru fișiere mari, abordarea sănătoasă rămâne un Route Handler (/api/upload) sau, și mai bine, un Server Action care doar generează un presigned URL de S3/R2, iar upload-ul propriu-zis îl faci direct din browser în bucket.
Webhook-uri și API-uri publice: Doar Route Handlers
Aici nu există dezbatere. Un webhook de la Stripe, LemonSqueezy sau GitHub are nevoie de un endpoint cu URL fix (de exemplu POST /api/webhooks/stripe), acces la request body-ul brut (raw body) pentru validarea semnăturii criptografice și un răspuns HTTP 200 sau 400 foarte clar.
Server Actions nu au un URL REST stabil. Ele funcționează intern prin POST-uri gestionate automat de Next.js, având header-e speciale precum Next-Action. Nu poți da acel endpoint către Stripe. Prin urmare, pentru integrări externe terțe sau dacă expui un API pentru o aplicație mobilă, Route Handlers (route.ts) sunt singura opțiune.
Regula mea de aur
Simplific totul printr-o regulă scurtă: dacă acțiunea este inițiată direct de un om din UI-ul tău React, folosește Server Actions. Dacă acțiunea vine de la un sistem extern, un cron job sau ai nevoie de un endpoint REST clasic, folosește Route Handlers.
Voi cum le combinați în proiecte? Mai folosiți /api pentru chestii interne sau ați mutat totul pe Server Actions?