---
title: 'Next.js 16 شرح Optimistic UI: الـ Server Actions لعام'
description: 'شرح عملي للإنتاج: إطلاق optimistic UI في Next.js 16.2 مع React 19.2 باستخدام useOptimistic، وuseActionState، وuseFormStatus، وrate limits، وrevalidatePath.'
url: https://nerdleveltech.com/ar/nextjs-16-server-actions-react-19-optimistic-ui-tutorial
language: ar
date: '2026-05-18'
category: frontend
tags:
  - nextjs
  - react
  - server-actions
  - useoptimistic
  - useactionstate
  - useformstatus
  - typescript
  - frontend
markdown_url: https://nerdleveltech.com/ar/nextjs-16-server-actions-react-19-optimistic-ui-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/nextjs-16-server-actions-react-19-optimistic-ui-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# Next.js 16 شرح Optimistic UI: الـ Server Actions لعام

**ملخص.** يبني هذا الدليل تطبيقاً مصغراً قابلاً للتشغيل بـ Next.js 16.2.6 + React 19.2.6 يقوم بإضافة وحذف الملاحظات من SQLite مع **واجهة مستخدم متفائلة (optimistic UI)** تصمد أمام الأخطاء، وإعادة تحميل الصفحة، وقيود معدل الطلبات. ستقوم بربط `useActionState` لحالة النموذج (form state)، و `useFormStatus` لزر الإرسال، و `useOptimistic` لتحديثات القائمة الفورية — مع نظرة فاحصة على فخ "الفشل الصامت" الذي يكسر معظم أكواد الشروحات التعليمية. الوقت الإجمالي: \~25 دقيقة.

## ما ستتعلمه

1. إنشاء هيكل مشروع Next.js 16.2.6 + TypeScript مع Drizzle + SQLite ليعمل المشروع محلياً بالكامل (بدون حساب سحابي)
2. كتابة Server Action مع مخطط Zod وعقد خطأ محدد النوع (typed error contract)
3. استخدام `useActionState` لإظهار أخطاء التحقق بجانب حقل الإدخال
4. استخدام `useFormStatus` داخل مكون فرعي `<SubmitButton>` لإظهار حالة الانتظار
5. استخدام `useOptimistic` لتحديثات القائمة الفورية — وتجنب فخ الفشل الصامت حيث لا تتراجع الأخطاء عن التغييرات
6. تحديث الصفحة التي يتم رندررتها على الخادم باستخدام `revalidatePath` ليعكس الـ DOM كل عملية تغيير
7. تأمين الـ Server Actions باستخدام `serverActions.allowedOrigins` وتحديد معدل الطلبات بنظام النافذة المنزلقة (sliding-window rate limit)

## المتطلبات الأساسية

- Node.js 24 LTS (دعم نشط حتى أكتوبر 2026)[^1] أو Node 22 Maintenance LTS (حتى أبريل 2027)
- `npm` 10+ (يأتي مع Node 24)
- مجلد فارغ يمكنك حذفه بـ `rm -rf` لاحقاً
- إلمام بمكونات دالة React والـ `async/await`

## الخطوة 1 — إنشاء هيكل المشروع

أصبحت الـ Server Actions مستقرة منذ إصدار Next.js 14. وفي Next.js 16، تأتي جنباً إلى جنب مع Turbopack بشكل افتراضي و React Compiler 1.0، ويقوم Next.js بتشفير معرف (ID) كل Action بمفتاح خاص بكل بناء (build) تذكر الوثائق أنه "يُعاد حسابه دورياً بين عمليات البناء لتعزيز الأمان."[^2] بالنسبة لعمليات النشر ذاتية الاستضافة على خوادم متعددة، يمكنك تثبيت هذا المفتاح باستخدام متغير البيئة `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` (مفتاح AES بتنسيق base64، بطول 16/24/32 بايت)؛ لا نحتاج إليه في هذا الدليل التعليمي الذي يعمل على عقدة واحدة.

ابدأ باستخدام `create-next-app` مثبتة على الإصدار 16.2.6 (وفقاً لمرجع واجهة سطر الأوامر الرسمي،[^11] فإن `--no-*` تلغي الخيارات الافتراضية و `--no-linter` هي الطريقة القياسية لتخطي سؤال الـ linter):

```bash
npx create-next-app@16.2.6 notes-app \
  --ts \
  --app \
  --no-src-dir \
  --no-tailwind \
  --no-linter \
  --import-alias "@/*"
cd notes-app
```

أضف تبعات وقت التشغيل المثبتة على الإصدارات التي تم التحقق منها على npm صباح يوم النشر:

```bash
npm install drizzle-orm@0.45.2 better-sqlite3@12.10.0 zod@4.4.3 \
            @upstash/ratelimit@2.0.8
npm install -D drizzle-kit@0.31.10 @types/better-sqlite3@7.6.13
```

تأكد من أن إصدارات Next.js و React و React DOM هي بالضبط الإصدارات المستخدمة في هذا الدليل:

```bash
npm ls next React React-dom
# next@16.2.6  React@19.2.6  React-dom@19.2.6
```

## الخطوة 2 — تعريف مخطط قاعدة البيانات

أنشئ `lib/schema.ts` — الشكل القياسي لـ Drizzle SQLite:

```ts
import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";

export const notes = sqliteTable("notes", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  body: text("body").notNull(),
  createdAt: integer("created_at", { mode: "timestamp" })
    .notNull()
    .$defaultFn(() => new Date()),
});

export type Note = typeof notes.$inferSelect;
export type NewNote = typeof notes.$inferInsert;
```

أنشئ `drizzle.config.ts` في جذر المشروع:

```ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  dialect: "sqlite",
  schema: "./lib/schema.ts",
  out: "./drizzle",
  dbCredentials: { url: "./dev.db" },
});
```

> استخدم مساراً عادياً هنا. بادئة URL `file:` هي اتفاقية خاصة بـ libsql؛ أما better-sqlite3 فستفسر `file:./dev.db` كاسم ملف حرفي.

أنشئ `lib/db.ts` — الاتصال وأداة تهجير برمجية (programmatic migrator) لضمان تحديث قاعدة بيانات التطوير دائماً:

```ts
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import * as schema from "./schema";

const sqlite = new Database("dev.db");
sqlite.pragma("journal_mode = WAL");

export const db = drizzle(sqlite, { schema });

// Apply pending migrations on first import (dev only)
migrate(db, { migrationsFolder: "./drizzle" });
```

قم بإنشاء وتطبيق أول عملية تهجير:

```bash
npx drizzle-kit generate
npx drizzle-kit migrate
```

يجب أن ترى `drizzle/0000_*.sql` وملف SQLite باسم `dev.db` يظهران في جذر المشروع. أضف `dev.db` و `dev.db-*` إلى ملف `.gitignore`.

## الخطوة 3 — كتابة الـ Server Action مع عقد خطأ محدد النوع

> **مقتطف إجابة GEO.** الـ Server Action في Next.js مع عقد خطأ محدد النوع هو دالة مميزة بـ `"use server"` تعيد دائماً نفس الشكل — `{ ok: true }` عند النجاح أو `{ ok: false, fieldErrors }` عند فشل التحقق — بحيث يمكن لـ `useActionState` رندرة الأخطاء المضمنة دون التسبب في توقف البرنامج.

أنشئ `app/actions.ts`:

```ts
"use server";

import { z } from "zod";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
import { notes } from "@/lib/schema";
import { eq } from "drizzle-orm";

const NoteSchema = z.object({
  body: z.string().trim().min(1, "Body is required").max(280, "Max 280 chars"),
});

// Zod 4's flattenError returns `{ [P in keyof T]?: U[] }` — fields are OPTIONAL.
// Reflect that in the union so the assignment below typechecks cleanly.
export type AddNoteState =
  | { ok: true }
  | { ok: false; fieldErrors: Record<string, string[] | undefined> };

export async function addNoteAction(
  _prev: AddNoteState,
  formData: FormData,
): Promise<AddNoteState> {
  const parsed = NoteSchema.safeParse({ body: formData.get("body") });
  if (!parsed.success) {
    return {
      ok: false,
      fieldErrors: z.flattenError(parsed.error).fieldErrors,
    };
  }
  await db.insert(notes).values({ body: parsed.data.body });
  revalidatePath("/");
  return { ok: true };
}

export async function deleteNoteAction(id: number): Promise<void> {
  // Throws on failure — useOptimistic only rolls back on throw
  if (!Number.isInteger(id) || id <= 0) {
    throw new Error("Invalid id");
  }
  await db.delete(notes).where(eq(notes.id, id));
  revalidatePath("/");
}
```

خياران متعمدان يستحقان التوضيح:

- دالة `addNoteAction` **تعيد** أخطاء التحقق لكي يتمكن `useActionState` من إظهارها بشكل مضمن. الـ Server Actions هي نقاط نهاية POST عامة، لذا فإن التحقق من المدخلات باستخدام Zod أمر غير قابل للتفاوض.[^3]
- دالة `deleteNoteAction` **ترمي خطأ (throws)** عند الفشل. هذا مهم في الخطوة 5: `useOptimistic` يقوم فقط بالتراجع التلقائي عندما ترمي الدالة غير المتزامنة خطأً. كائن الخطأ المُعاد لن يؤدي إلى التراجع، وستصبح واجهة المستخدم الخاصة بك غير متزامنة مع قاعدة البيانات.[^4]

كلاهما يستدعي `revalidatePath("/")` لإخبار Next.js بأن بيانات المسار الرئيسي قد تغيرت؛ ستقوم عملية الرندرة التالية بتنفيذ Server Component وإعادة جلب قائمة الصفوف.[^5] واجهة التخزين المؤقت طويلة المدى الموصى بها في Next.js 16 هي توجيه `"use cache"` (Cache Components)، ولكن بالنسبة لدليل Server Action بسيط، فإن الاستعلام المباشر بالإضافة إلى `revalidatePath` يحافظ على التركيز على الـ Actions وليس التخزين المؤقت.

## الخطوة 4 — قراءة الملاحظات من Server Component

أنشئ `lib/queries.ts`:

```ts
import { db } from "./db";
import { notes } from "./schema";
import { desc } from "drizzle-orm";

export async function listNotes() {
return db.select().from(notes).orderBy(desc(notes.createdAt));
}
```

لأن الصفحة عبارة عن Server Component، يتم تشغيل هذا الاستعلام مع كل طلب لـ `/`. عندما يستدعي أي تغيير `revalidatePath("/")`، يقوم Next.js بحذف أي ذاكرة تخزين مؤقت للرندرة في الذاكرة لهذا المسار لكي يرى الزائر التالي صفوفاً جديدة.

## الخطوة 5 — بناء واجهة المستخدم المتفائلة

هنا تختصر معظم الشروحات التعليمية الطريق. وثائق React الرسمية صريحة: التحديث المتفائل يتراجع فقط عندما **ترمي** الـ Action خطأً.[^4] إذا أعادت الـ Action كائناً يحتوي على خطأ، فستظل الحالة المتفائلة قائمة حتى تتغير خصائص (props) المكون الأب. سنتجنب هذا الفخ باستخدام `try/catch` واحدة وحالة احتياطية `setError`.

أنشئ `app/notes.tsx`:

```tsx
"use client";

import { useActionState, useOptimistic, useState, startTransition } from "React";
import { useFormStatus } from "React-dom";
import {
  addNoteAction,
  deleteNoteAction,
  type AddNoteState,
} from "./actions";
import type { Note } from "@/lib/schema";

type OptimisticNote = Note & { pending?: boolean };

function SubmitButton() {
  // Must be in a CHILD of <form>, not the form component itself.
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Adding…" : "Add note"}
    </button>
  );
}

export default function Notes({ initialNotes }: { initialNotes: Note[] }) {
  const [formState, formAction] = useActionState<AddNoteState, FormData>(
    addNoteAction,
    { ok: true },
  );

  const [optimisticNotes, applyOptimistic] = useOptimistic<
    OptimisticNote[],
    { type: "delete"; id: number }
  >(initialNotes, (current, action) => {
    if (action.type === "delete") {
      return current.map((n) =>
        n.id === action.id ? { ...n, pending: true } : n,
      );
    }
    return current;
  });

  const [deleteError, setDeleteError] = useState<string | null>(null);

  async function handleDelete(id: number) {
    setDeleteError(null);
    startTransition(async () => {
      applyOptimistic({ type: "delete", id });
      try {
        await deleteNoteAction(id);
        // revalidatePath inside the action will refresh `initialNotes`
        // on the next server render
      } catch (err) {
        // Automatic rollback already happened because the action threw.
        setDeleteError(err instanceof Error ? err.message : "Delete failed");
      }
    });
  }

  return (
    <section>
      <form action={formAction}>
        <label htmlFor="body">New note</label>
        <input id="body" name="body" maxLength={280} required />
        <SubmitButton />
        {formState.ok === false && (
          <p role="alert" aria-live="polite">
            {formState.fieldErrors.body?.[0]}
          </p>
        )}
      </form>

      {deleteError && (
        <p role="alert" aria-live="polite">
          {deleteError}
        </p>
      )}

      <ul>
        {optimisticNotes.map((n) => (
          <li
            key={n.id}
            style={{ opacity: n.pending ? 0.4 : 1, textDecoration: n.pending ? "line-through" : "none" }}
          >
            {n.body}
            <button
              type="button"
              disabled={n.pending}
              onClick={() => handleDelete(n.id)}
            >
              {n.pending ? "Deleting…" : "Delete"}
            </button>
          </li>
        ))}
      </ul>
    </section>
  );
}
```

ما الذي يقوم بالعمل هنا:

- `useActionState` تعيد `[state, dispatch, isPending]` — نحن نتجاهل `isPending` لأن `useFormStatus` يتولى أمر الزر. توقيع الـ reducer لـ form action هو `(prevState, formData)`، حيث يكون FormData هو الوسيط الثاني.[^6]
- `useFormStatus` يقرأ فقط حالة النموذج **الأب** له، لذا يجب أن يتواجد داخل مكون فرعي — هنا هو `<SubmitButton>`.[^7]
- `useOptimistic` يأخذ القيمة القياسية `initialNotes` بالإضافة إلى reducer نقي. يقوم الـ reducer بتمييز العنصر بـ `pending: true` بدلاً من حذفه، لكي يبهت الصف بوضوح أثناء رحلة الطلب. عندما يؤكد الخادم العملية (أو ترمي الـ action خطأً)، يتقارب React مع قيمة الأب في الرندرة التالية.[^4]
- كتلة `try/catch` هي شبكة الأمان. على الرغم من أن `deleteNoteAction` ترمي خطأً وتفعل التراجع التلقائي في React، إلا أنك لا تزال بحاجة لإظهار الخطأ للمستخدم.

قم برندرتها من `app/page.tsx`:

```tsx
import { listNotes } from "@/lib/queries";
import Notes from "./notes";

export default async function Page() {
  const initialNotes = await listNotes();
  return (
    <main>
      <h1>Notes</h1>
      <Notes initialNotes={initialNotes} />
    </main>
  );
}
```

## الخطوة 6 — تأمين الـ action: تحديد معدل الطلبات (rate limit) والحماية من CSRF

تبدو Server Actions مثل استدعاءات RPC ولكنها تُرسل كطلبات HTTP POST عادية. يقوم FastAPI بتقليل مخاطر CSRF تلقائيًا عن طريق مقارنة ترويسات (headers) `Origin` و `Host` ورفض الطلب في حالة عدم التطابق، وهو أمر كافٍ للصفحات التي تنتمي لنفس الأصل (same-origin).[^8] إذا كان تطبيقك يعمل خلف وكيل عكسي (reverse proxy) أو كنت تعرض معاينات على نطاق مختلف، فيجب عليك إدراج تلك الأصول صراحةً:

`next.config.ts`:

```ts
import type { NextConfig } from "next";

const config: NextConfig = {
  experimental: {
    serverActions: {
      allowedOrigins: ["my-proxy.example.com", "*.my-proxy.example.com"],
      bodySizeLimit: "1mb", // افتراضي؛ قم بزيادته لرفع الملفات
    },
  },
};

export default config;
```

كلا الخيارين موجودان داخل `experimental.serverActions` في التوثيق الحالي.[^9]

بعد ذلك، قم بتحديد معدل الطلبات لكل عنوان IP لمنع استغلال الـ action بشكل سيء. توفر حزمة `@upstash/ratelimit` إصدار 2.0.8 محدد معدل بنظام النافذة المنزلقة (sliding-window):

`lib/ratelimit.ts`:

```ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/Redis";

export const noteWriteLimiter = new Ratelimit({
  Redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, "10 s"),
  prefix: "notes-write",
});
```

أضف ذلك إلى الـ action:

```ts
import { headers } from "next/headers";
import { noteWriteLimiter } from "@/lib/ratelimit";

export async function addNoteAction(/* ... */): Promise<AddNoteState> {
  const ip = (await headers()).get("x-forwarded-for")?.split(",")[0] ?? "anon";
  const { success } = await noteWriteLimiter.limit(ip);
  if (!success) {
    return { ok: false, fieldErrors: { body: ["طلبات كثيرة جدًا، تمهل قليلاً."] } };
  }
  // ...التحقق الحالي بـ Zod + الإدراج
}
```

للتطوير المحلي، قم بتوجيه `UPSTASH_REDIS_REST_URL` و `UPSTASH_REDIS_REST_TOKEN` إلى قاعدة بيانات Upstash مجانية (تقرأ SDK الخاصة بـ Redis كليهما من البيئة) أو قم بحماية استدعاء تحديد المعدل بعلامة ميزة (feature flag) وتخطاه عندما تكون متغيرات البيئة مفقودة — يتطلب `Ratelimit` عميلاً متوافقًا مع Redis.

## الخطوة 7 — التحقق

قم بتشغيل خادم التطوير:

```bash
npm run dev
# - المحلي:  http://localhost:3000
```

في المتصفح:

1. افتح `http://localhost:3000`. أضف ملاحظة — يظهر زر الإرسال "Adding…" أثناء تنفيذ الـ action (هذا هو `useFormStatus`).
2. أرسل نموذجًا فارغًا — يرفض Zod الطلب وتظهر رسالة الخطأ في مكانها. (لقد قمنا بربط `useOptimistic` بمسار **الحذف** فقط عن قصد، لأن الحذف هو المكان الذي تهم فيه قصة التراجع؛ تستخدم عمليات الإضافة عقد الخطأ المتزامن الخاص بـ `useActionState` بدلاً من ذلك.)
3. انقر فوق حذف في أحد الصفوف — يختفي فورًا (بشكل متفائل)، ثم يختفي نهائيًا بمجرد تأكيد الخادم عبر `revalidatePath`.
4. لإثبات نجاح التراجع، قم بتعديل `deleteNoteAction` مؤقتًا لـ `throw new Error("simulated")` قبل استدعاء قاعدة البيانات. انقر فوق حذف — يختفي الصف، ثم يعود فجأة، ويقرأ الخطأ المضمن "simulated". تراجع عن التغيير.

تأكد من أن نقطة نهاية الـ action مقفولة على طلبات POST من نفس الأصل فقط:

```bash
# يتم رفض طلب POST من أصل مختلف — تختلف الاستجابة الدقيقة (عادةً 403 أو إعادة توجيه)
curl -i -X POST http://localhost:3000 \
  -H "Origin: https://evil.example.com" \
  -H "Content-Type: text/plain" \
  --data ""
# HTTP/1.1 403 Forbidden    (أو إعادة التوجيه للصفحة الرئيسية من حماية CSRF)
```

## الأخطاء الشائعة

| العرض                                                                            | السبب المرجح                                                                                  | الحل                                                                                                                 |
| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `useFormStatus` دائمًا `pending: false`                                          | تم استدعاء الـ Hook في مكون النموذج نفسه، وليس في مكون تابع                                   | انقله إلى مكون تابع `<SubmitButton>`[^7]                                                                             |
| تحذير "An async function with useActionState was called outside of a transition" | لقد قمت باستدعاء `dispatch()` يدويًا بدلاً من عبر خاصية `<form action>`                       | قم بتغليفه في `startTransition(() => dispatch(payload))`                                                             |
| الحذف المتفائل يظل رماديًا للأبد                                                 | الـ action الخاص بك يعيد `{error: ...}` بدلاً من رمي خطأ (throw)                              | `useOptimistic` يتراجع فقط عند رمي خطأ — قم بتغيير الـ action ليرمي خطأ أو قم بتحديث حالة المكون الأب[^4]            |
| مدخلات النموذج تفرغ بعد النجاح                                                   | يقوم React 19 بإعادة تعيين تلقائي لـ `<form action={fn}>` غير المتحكم فيه بعد نجاح الـ action | استخدم مدخلات متحكم فيها، أو `requestFormReset` من `React-dom` للإدارة بشكل صريح[^10]                                |
| `Body exceeded 1MB limit` عند رفع ملف                                            | الحد الافتراضي لـ `serverActions.bodySizeLimit` هو 1 ميجابايت                                 | قم بزيادته في `next.config.ts` (`bodySizeLimit: '5mb'`) أو استخدم نمط الرفع عبر رابط موقع مسبقًا (presigned-URL)[^9] |
| الـ action يعمل ولكن الصفحة لا تتحدث                                             | نسيت استدعاء `revalidatePath` داخل الـ action                                                 | أضف `revalidatePath("/")` بعد كل عملية تعديل حتى يتم إعادة رندر الـ Server Component[^5]                             |

## الخطوات التالية

- قم بربط نفس النمط بملف تعريف تخزين مؤقت أطول باستخدام [`use cache` و `cacheLife` في FastAPI 16](https://nerdleveltech.com/nextjs-16-streaming-suspense-use-cache-tutorial) لتبقى استعلامات القراءة غير مكلفة.
- إذا كنت قادمًا من Pages Router، فإن [دليل الانتقال إلى App Router](https://nerdleveltech.com/nextjs-pages-router-to-app-router-migration-tutorial) يغطي تغييرات التوجيه التي تفتح المجال لاستخدام Server Actions.
- قم بتنشيط معلوماتك الأساسية حول [React props و state](https://nerdleveltech.com/react-props-and-state-2025) قبل تقديم المزيد من الـ hooks.

## الحواشي

[^1]: جدول إصدارات Node.js — دخل Node 24 مرحلة LTS النشطة في 28 أكتوبر 2025 ويظل كذلك حتى أكتوبر 2026؛ دخل Node 22 مرحلة LTS للصيانة في أكتوبر 2025 مع دعم حتى أبريل 2027. انظر <https://endoflife.date/nodejs> وصفحة إصدارات nodejs.org لمعرفة تواريخ المراحل الرسمية.

[^2]: منشور إصدار FastAPI 16 يوثق استقرار Turbopack وكون React Compiler 1.0 افتراضيًا (<https://nextjs.org/blog/next-16>). آلية تشفير معرف Server Action، وتخزين البناء المؤقت لمدة 14 يومًا، ومتغير البيئة `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` لاتساق الخوادم المتعددة المستضافة ذاتيًا موثقة في <https://nextjs.org/docs/app/guides/data-security> ("Secure action IDs" و "Overwriting encryption keys").

[^11]: مرجع واجهة سطر أوامر `create-next-app` — عائلة `--no-*` تلغي الخيارات الافتراضية؛ علامة "تخطي المدقق" الرسمية في FastAPI 16 هي `--no-linter`. <https://nextjs.org/docs/app/api-reference/cli/create-next-app>

[^3]: إرشادات أمان Server Actions في FastAPI — "يجب دائمًا معاملة Server Actions على أنها معادية، ويجب التحقق من المدخلات." <https://nextjs.org/blog/security-nextjs-server-components-actions>

[^4]: توثيق React — تعمق في `useOptimistic` "ماذا يحدث عندما يفشل الـ Action": "إذا رمى الـ Action خطأً، فإن الـ Transition ينتهي، ويقوم React بالرندر باستخدام أي قيمة كانت لـ `value` حاليًا." <https://react.dev/reference/react/useOptimistic>

[^5]: `revalidatePath` تقوم بإلغاء صلاحية البيانات التي تم جلبها على مسار معين في الطلب التالي. <https://nextjs.org/docs/app/API-reference/functions/revalidatePath>

[^6]: مرجع `useActionState` — مع `<form action={dispatch}>`، يكون توقيع الـ reducer هو `(previousState, formData)`. <https://React.dev/reference/React/useActionState>

[^7]: مرجع `useFormStatus` — "يجب استدعاؤه من مكون يتم رندرة (render) داخله `<form>`" و "لن يعيد معلومات الحالة لأي `<form>` يتم رندرته في نفس المكون." <https://React.dev/reference/React-dom/hooks/useFormStatus>

[^8]: توثيق Next.js — تقوم Server Actions بمقارنة هيدر `Origin` بهيدر `Host` وترفض الطلب في حالة عدم التطابق كإجراء مدمج للحماية من CSRF بالإضافة إلى ملفات تعريف الارتباط (cookies) من نوع `SameSite`. <https://nextjs.org/docs/app/guides/data-security>

[^9]: إعدادات `serverActions` — يتم تكوين `bodySizeLimit` (الافتراضي `1mb`) و `allowedOrigins` تحت `experimental.serverActions` في ملف `next.config.ts`. <https://nextjs.org/docs/app/API-reference/config/next-config-js/serverActions>

[^10]: منشور إطلاق React 19 — يقوم `<form action={fn}>` تلقائياً بإعادة تعيين المدخلات غير المتحكم بها (uncontrolled inputs) بعد اكتمال الـ action. التحكم اليدوي متاح عبر `requestFormReset` في `React-dom`. <https://React.dev/blog/2024/12/05/React-19>
