Server Actions في Next.js — أداء خارق بدون API
تعلّم كيف تستخدم Server Actions لإنشاء mutations آمنة وسريعة بدون كتابة API routes، مع progressive enhancement تلقائي.
ما هي Server Actions؟
Server Actions هي دوال تعمل على الخادم وتستطيع استدعاؤها مباشرة من المكوّن — بدون كتابة API route، وبدون fetch.
// قبل Server Actions
async function handleSubmit(data: FormData) {
const res = await fetch("/api/posts", {
method: "POST",
body: data,
});
// ...
}
// مع Server Actions
async function createPost(data: FormData) {
"use server";
await db.post.create({ data: { title: data.get("title") as string } });
revalidatePath("/blog");
}كلتاهما تنفذ نفس الشيء، لكن الثانية أبسط وأكثر أمانًا.
كيف تعمل؟
عند وضع "use server" في دالة، يحوّلها Next.js إلى endpoint مشفّر تلقائيًا. المتصفح يستدعي هذا الـ endpoint بدون أن تكتب API route.
المتصفح → يستدعي الدالة → Next.js يُرسل POST إلى endpoint مشفّر → الخادم ينفّذ الكود → يُرجع النتيجةلا يمكن للمتصفح رؤية كود الـ Server Action أبدًا. فقط اسم الـ endpoint المُشفَّر يُرسَل.
الاستخدام مع النماذج
أبسط مثال
// app/blog/new/page.tsx
export default function NewPostPage() {
async function createPost(formData: FormData) {
"use server";
const title = formData.get("title") as string;
const content = formData.get("content") as string;
await db.post.create({ data: { title, content } });
redirect("/blog");
}
return (
<form action={createPost}>
<input name="title" placeholder="العنوان" required />
<textarea name="content" placeholder="المحتوى" required />
<button type="submit">نشر</button>
</form>
);
}يعمل هذا حتى بدون JavaScript في المتصفح — هذا هو الـ progressive enhancement.
مع useActionState
لإضافة حالة التحميل والأخطاء:
"use client";
import { useActionState } from "react";
import { createPost } from "./actions";
type State = { error?: string; success?: boolean };
export function NewPostForm() {
const [state, action, isPending] = useActionState<State, FormData>(
createPost,
{}
);
return (
<form action={action}>
{state.error && (
<p className="text-red-600">{state.error}</p>
)}
{state.success && (
<p className="text-green-600">تم النشر بنجاح!</p>
)}
<input name="title" placeholder="العنوان" required />
<button disabled={isPending}>
{isPending ? "جارٍ النشر..." : "نشر المقال"}
</button>
</form>
);
}// actions.ts
"use server";
export async function createPost(
_prevState: { error?: string },
formData: FormData
) {
const title = formData.get("title") as string;
if (title.length < 5) {
return { error: "العنوان يجب أن يكون 5 أحرف على الأقل" };
}
try {
await db.post.create({ data: { title } });
revalidatePath("/blog");
return { success: true };
} catch {
return { error: "فشل النشر، حاول مجددًا" };
}
}Optimistic Updates
تحديث الواجهة فورًا قبل انتهاء العملية على الخادم:
"use client";
import { useOptimistic, useTransition } from "react";
import { toggleLike } from "./actions";
export function LikeButton({ postId, initialLikes }: Props) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
initialLikes,
(state, increment: number) => state + increment
);
const [isPending, startTransition] = useTransition();
const handleLike = () => {
startTransition(async () => {
addOptimisticLike(1); // فوري في الواجهة
await toggleLike(postId); // يعمل في الخلفية
});
};
return (
<button onClick={handleLike} disabled={isPending}>
❤️ {optimisticLikes}
</button>
);
}الأمان
التحقق من المصادقة
"use server";
import { auth } from "@/lib/auth";
export async function deletePost(postId: string) {
// تحقق دائمًا من المستخدم داخل الـ action
const session = await auth();
if (!session?.user) throw new Error("غير مصرح");
// تحقق من الصلاحيات
const post = await db.post.findUnique({ where: { id: postId } });
if (post?.authorId !== session.user.id) throw new Error("غير مصرح");
await db.post.delete({ where: { id: postId } });
revalidatePath("/blog");
}لا تثق أبدًا في المدخلات القادمة من المتصفح. تحقق من الهوية والصلاحيات داخل كل Server Action بشكل مستقل.
متى تستخدم Server Actions؟
| الحالة | الأنسب |
|---|---|
| Forms بسيطة | Server Action مباشر |
| Real-time, WebSocket | API Route |
| External webhook | API Route |
| CRUD عادي | Server Action |
| File upload كبير | API Route مع streaming |
| Optimistic UI | Server Action + useOptimistic |
الخلاصة
Server Actions تُبسّط كثيرًا من كود الـ full-stack — لا fetch، لا API routes، لا CORS. الكود أقل، والأمان أفضل لأن المنطق يعمل فقط على الخادم.