Server Actions در Next.js: از فرم تا کش و پاکسازی داده
نویسنده: آرش لطیفی
راهنمای عملی Server Actions در Next.js App Router: ساخت اکشن سروری، اعتبارسنجی، revalidatePath و revalidateTag، مدیریت خطا و اشتباهات رایج کش.
TL;DR: Server Actions یعنی یک تابع سمت سرور که از فرم صدا زده میشود؛ بعد از تغییر داده باید کش را با
revalidatePathیاrevalidateTagباطل کنی، وگرنه کاربر تغییر را نمیبیند.
اگر تا حالا برای هر عملیات یک Route Handler ساختهای و بعد با fetch صدا زدهای، با Server Actions خیلی کمتر کد مینویسی. اینجا یک مسیر کامل از فرم تا دیتابیس و باطل کردن کش را میبینیم.
اگر تازهکاری، اول مرزهای Server و Client را مرور کن، چون همهچیز به درست بودن مرز بستگی دارد.
اکشن سروری چیست
هر تابعی که با use server یا فایل actions.ts علامت بخورد، روی سرور اجرا میشود. فرم مستقیم آن را صدا میزند و کد سمت کلاینت داخل باندل نمیآید. یعنی منطق حساس مثل قیمت نهایی یا بررسی نقش کاربر هرگز به کلاینت لو نمیرود.
// app/actions.ts
"use server";
import { z } from "zod";
import { db } from "@/lib/db";
const productSchema = z.object({
name: z.string().min(2),
price: z.number().positive(),
});
export async function createProduct(prevState: State, formData: FormData) {
const session = await auth();
if (!session?.user) return { error: "unauthorized" };
const parsed = productSchema.safeParse({
name: formData.get("name"),
price: Number(formData.get("price")),
});
if (!parsed.success) return { error: "invalid" };
const product = await db.product.create({ data: parsed.data });
revalidatePath("/admin/products");
return { success: product.slug };
}
نکته مهم: اعتبارسنجی Zod سمت سرور اختیاری نیست. اگر قبلاً الگوی اعتبارسنجی مشترک را ساختهای، راهنمای فرم و Zod همان مسیر را کاملتر توضیح میدهد.
اتصال به فرم با useActionState
کامپوننت فرم فقط یک action میگیرد و وضعیت بازگشتی را میخواند.
متغیر pending همان چیزی است که کاربر را منتظر نگه میدارد؛ بدون آن، دکمه دوبار کلیک میشود و رکورد تکراری میسازد.
باطل کردن کش؛ مهمترین بخش کار
در App Router نتایج fetch و Route Handlerها کش میشوند. اگر بعد از ایجاد محصول کش را باطل نکنی، لیست محصولات همان نسخه قدیمی را نشان میدهد.
| ابزار | کاربرد | مثال |
|---|---|---|
| revalidatePath | باطل کردن مسیر مشخص | revalidatePath("/admin/products") |
| revalidateTag | باطل کردن همهی دادههای یک تگ | revalidateTag("products") |
| redirect | هدایت بعد از موفقیت | redirect("/admin/products") |
قانون سرانگشتی: اگر صفحهای با fetch داده میگیرد، برایش تگ بگذار و در اکشن همان تگ را باطل کن. درباره تفاوت کش در نسخه ۱۵ و PPR هم این راهنما دقیقتر توضیح داده.
اشتباهات رایج
کش کردن دادههای احرازهویت: اگر خروجی fetch به کاربر وابسته است، آن را کش نکن؛ cache: "no-store" یا کوکی در کلید. کش اشتباه یعنی نشت داده یک کاربر به کاربر دیگر.
برگرداندن شیء غیرقابل سریال: فقط داده ساده (رشته، عدد، boolean) برگردان. کلاس و Promise از مرز اکشن رد نمیشود.
ندادن بازخورد به فرم: اگر pending را مدیریت نکنی، کاربر نمیداند درخواست ثبت شده یا نه. برای الگوی کامل حالتهای بارگذاری و خطا این راهنما را ببین.
استفاده از اکشن برای خواندن: اکشن برای mutation است. برای گرفتن داده از Server Component استفاده کن.
چکلیست بازبینی
- [ ] هر اکشن با
auth()بررسی دسترسی میکند - [ ] اعتبارسنجی
Zodسمت سرور انجام میشود - [ ] دادههای احرازهویتی با تگ کش نمیشوند
- [ ] بعد از هر mutation، کش باطل میشود
- [ ]
pendingدر فرم نمایش داده میشود - [ ] خطا با
role="alert"به کاربر اعلام میشود
سوالات پرتکرار
Q: تفاوت Server Action با Route Handler چیست؟
Route Handler برای endpoint است و معمولاً با کلاینت بیرونی مصرف میشود. Server Action برای mutation داخلی از کامپوننتهای خودت است و کد کمتری میخواهد.
Q: آیا باید همهچیز را با اکشن انجام دهم؟
نه. خواندن داده در Server Component سادهتر و سریعتر است. اکشن را برای نوشتن و تغییر وضعیت نگه دار.
Q: خطای سرور چطور به کاربر برسد؟
بهجای throw کردن، یک آبجکت ساده برگردان و با role="alert" نمایش بده. throw کردن صفحه خطای کلی میسازد.