إجراءات الخادم والتحولات (Server Actions and Mutations)

إجراءات الخادم (Server Actions) هي وظائف غير متزامنة يتم تنفيذها على الخادم. يمكن استخدامها في مكونات الخادم والعميل للتعامل مع إرسال النماذج وتحولات البيانات في تطبيقات Next.js.

🎥 شاهد: تعلم المزيد عن النماذج والتحولات باستخدام إجراءات الخادم → YouTube (10 دقائق).

الاصطلاح

يمكن تعريف إجراء الخادم باستخدام توجيه React "use server". يمكنك وضع التوجيه في أعلى دالة async لتمييزها كإجراء خادم، أو في أعلى ملف منفصل لتمييز جميع الصادرات من هذا الملف كإجراءات خادم.

مكونات الخادم (Server Components)

يمكن لمكونات الخادم استخدام توجيه "use server" على مستوى الدالة أو على مستوى الوحدة. لتضمين إجراء خادم، أضف "use server" في أعلى جسم الدالة:

// مكون الخادم
export default function Page() {
  // إجراء الخادم
  async function create() {
    'use server'

    // ...
  }

  return (
    // ...
  )
}
// مكون الخادم
export default function Page() {
  // إجراء الخادم
  async function create() {
    'use server'

    // ...
  }

  return (
    // ...
  )
}

مكونات العميل (Client Components)

يمكن لمكونات العميل استيراد الإجراءات التي تستخدم توجيه "use server" على مستوى الوحدة فقط.

لاستدعاء إجراء خادم في مكون عميل، أنشئ ملفًا جديدًا وأضف توجيه "use server" في أعلى الملف. سيتم تمييز جميع الدوال داخل الملف كإجراءات خادم يمكن إعادة استخدامها في كل من مكونات العميل والخادم:

'use server'

export async function create() {
  // ...
}
'use server'

export async function create() {
  // ...
}
import { create } from '@/app/actions'

export function Button() {
  return (
    // ...
  )
}
import { create } from '@/app/actions'

export function Button() {
  return (
    // ...
  )
}

يمكنك أيضًا تمرير إجراء خادم إلى مكون عميل كخاصية:

<ClientComponent updateItem={updateItem} />
app/client-component.jsx
'use client'

export default function ClientComponent({ updateItem }) {
  return <form action={updateItem}>{/* ... */}</form>
}

السلوك

  • يمكن استدعاء إجراءات الخادم باستخدام سمة action في عنصر <form>:
    • تدعم مكونات الخادم التحسين التدريجي افتراضيًا، مما يعني أن النموذج سيتم إرساله حتى إذا لم يتم تحميل JavaScript بعد أو تم تعطيله.
    • في مكونات العميل، ستقوم النماذج التي تستدعي إجراءات الخادم بوضع عمليات الإرسال في قائمة الانتظار إذا لم يتم تحميل JavaScript بعد، مع إعطاء الأولوية لترطيب العميل.
    • بعد الترطيب، لا يقوم المتصفح بالتحديث عند إرسال النموذج.
  • لا تقتصر إجراءات الخادم على <form> ويمكن استدعاؤها من معالجي الأحداث، وuseEffect، ومكتبات الطرف الثالث، وعناصر النموذج الأخرى مثل <button>.
  • تندمج إجراءات الخادم مع بنية التخزين المؤقت وإعادة التحقق من Next.js. عند استدعاء إجراء، يمكن لـ Next.js إرجاع واجهة المستخدم المحدثة والبيانات الجديدة في جولة واحدة من الخادم.
  • خلف الكواليس، تستخدم الإجراءات طريقة POST، وهذه الطريقة فقط هي التي يمكنها استدعاؤها.
  • يجب أن تكون وسيطات وقيمة إرجاع إجراءات الخادم قابلة للتسلسل بواسطة React. راجع وثائق React للحصول على قائمة بالمعاملات وقيم الإرجاع القابلة للتسلسل.
  • إجراءات الخادم هي دوال. هذا يعني أنه يمكن إعادة استخدامها في أي مكان في تطبيقك.
  • ترث إجراءات الخادم بيئة التشغيل من الصفحة أو التخطيط الذي تستخدمه.
  • ترث إجراءات الخادم تكوين مقطع المسار من الصفحة أو التخطيط الذي تستخدمه، بما في ذلك الحقول مثل maxDuration.

أمثلة

النماذج

يمتد React عنصر <form> في HTML للسماح باستدعاء إجراءات الخادم باستخدام خاصية action.

عند استدعائها في نموذج، يتلقى الإجراء تلقائيًا كائن FormData. لا تحتاج إلى استخدام React useState لإدارة الحقول، بدلاً من ذلك، يمكنك استخراج البيانات باستخدام طرق FormData الأصلية:

export default function Page() {
  async function createInvoice(formData: FormData) {
    'use server'

    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }

    // تحويل البيانات
    // إعادة التحقق من التخزين المؤقت
  }

  return <form action={createInvoice}>...</form>
}
export default function Page() {
  async function createInvoice(formData) {
    'use server'

    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }

    // تحويل البيانات
    // إعادة التحقق من التخزين المؤقت
  }

  return <form action={createInvoice}>...</form>
}

معلومة مفيدة:

تمرير وسيطات إضافية

يمكنك تمرير وسيطات إضافية إلى إجراء خادم باستخدام طريقة bind في JavaScript.

'use client'

import { updateUser } from './actions'

export function UserProfile({ userId }: { userId: string }) {
  const updateUserWithId = updateUser.bind(null, userId)

  return (
    <form action={updateUserWithId}>
      <input type="text" name="name" />
      <button type="submit">تحديث اسم المستخدم</button>
    </form>
  )
}
'use client'

import { updateUser } from './actions'

export function UserProfile({ userId }) {
  const updateUserWithId = updateUser.bind(null, userId)

  return (
    <form action={updateUserWithId}>
      <input type="text" name="name" />
      <button type="submit">تحديث اسم المستخدم</button>
    </form>
  )
}

سيستقبل إجراء الخادم وسيطة userId، بالإضافة إلى بيانات النموذج:

app/actions.js
'use server'

export async function updateUser(userId, formData) {
  // ...
}

معلومة مفيدة:

  • بديل هو تمرير الوسيطات كحقول إدخال مخفية في النموذج (مثل <input type="hidden" name="userId" value={userId} />). ومع ذلك، ستكون القيمة جزءًا من HTML المقدم ولن تكون مشفرة.
  • يعمل .bind في كل من مكونات الخادم والعميل. كما يدعم التحسين التدريجي.

حالات الانتظار

يمكنك استخدام خطاف React useFormStatus لعرض حالة انتظار أثناء إرسال النموذج.

  • useFormStatus تُرجع الحالة لنموذج معين <form>، لذا يجب تعريفها كعنصر فرعي لعنصر <form>.
  • useFormStatus هو خطاف React وبالتالي يجب استخدامه في مكون عميل.
'use client'

import { useFormStatus } from 'react-dom'

export function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" disabled={pending}>
      إضافة
    </button>
  )
}
'use client'

import { useFormStatus } from 'react-dom'

export function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" disabled={pending}>
      إضافة
    </button>
  )
}

يمكن بعد ذلك تضمين <SubmitButton /> في أي نموذج:

import { SubmitButton } from '@/app/submit-button'
import { createItem } from '@/app/actions'

// مكون الخادم
export default async function Home() {
  return (
    <form action={createItem}>
      <input type="text" name="field-name" />
      <SubmitButton />
    </form>
  )
}
import { SubmitButton } from '@/app/submit-button'
import { createItem } from '@/app/actions'

// مكون الخادم
export default async function Home() {
  return (
    <form action={createItem}>
      <input type="text" name="field-name" />
      <SubmitButton />
    </form>
  )
}

التحقق من صحة الخادم ومعالجة الأخطاء

نوصي باستخدام التحقق من صحة HTML مثل required و type="email" للتحقق الأساسي من صحة النموذج على جانب العميل.

للتحقق المتقدم من صحة الخادم، يمكنك استخدام مكتبة مثل zod للتحقق من حقول النموذج قبل تحويل البيانات:

'use server'

import { z } from 'zod'

const schema = z.object({
  email: z.string({
    invalid_type_error: 'بريد إلكتروني غير صالح',
  }),
})

export default async function createUser(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
  })

  // العودة مبكرًا إذا كانت بيانات النموذج غير صالحة
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }

  // تحويل البيانات
}
'use server'

import { z } from 'zod'

const schema = z.object({
  email: z.string({
    invalid_type_error: 'بريد إلكتروني غير صالح',
  }),
})

export default async function createsUser(formData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
  })

  // العودة مبكرًا إذا كانت بيانات النموذج غير صالحة
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }

  // تحويل البيانات
}

بعد التحقق من صحة الحقول على الخادم، يمكنك إرجاع كائن قابل للتسلسل في إجراءك واستخدام خطاف React useFormState لعرض رسالة للمستخدم.

  • عن طريق تمرير الإجراء إلى useFormState، يتغير توقيع الدالة للإجراء لتلقي معلمة جديدة prevState أو initialState كأول وسيطة لها.
  • useFormState هو خطاف React وبالتالي يجب استخدامه في مكون عميل.
'use server'

export async function createUser(prevState: any, formData: FormData) {
  // ...
  return {
    message: 'الرجاء إدخال بريد إلكتروني صالح',
  }
}
'use server'

export async function createUser(prevState, formData) {
  // ...
  return {
    message: 'الرجاء إدخال بريد إلكتروني صالح',
  }
}

ثم، يمكنك تمرير إجراءك إلى خطاف useFormState واستخدام state المُرجعة لعرض رسالة خطأ.

'use client'

import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'

const initialState = {
  message: '',
}

export function Signup() {
  const [state, formAction] = useFormState(createUser, initialState)

  return (
    <form action={formAction}>
      <label htmlFor="email">البريد الإلكتروني</label>
      <input type="text" id="email" name="email" required />
      {/* ... */}
      <p aria-live="polite" className="sr-only">
        {state?.message}
      </p>
      <button>اشتراك</button>
    </form>
  )
}
'use client'

import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'

const initialState = {
  message: '',
}

export function Signup() {
  const [state, formAction] = useFormState(createUser, initialState)

  return (
    <form action={formAction}>
      <label htmlFor="email">البريد الإلكتروني</label>
      <input type="text" id="email" name="email" required />
      {/* ... */}
      <p aria-live="polite" className="sr-only">
        {state?.message}
      </p>
      <button>اشتراك</button>
    </form>
  )
}

معلومة مفيدة:

  • قبل تحويل البيانات، يجب عليك دائمًا التأكد من أن المستخدم مخول أيضًا لتنفيذ الإجراء. راجع المصادقة والتفويض.

التحديثات المتفائلة

يمكنك استخدام خطاف React useOptimistic لتحديث واجهة المستخدم بشكل متفائل قبل انتهاء إجراء الخادم، بدلاً من انتظار الرد:

'use client'

import { useOptimistic } from 'react'
import { send } from './actions'

type Message = {
  message: string
}

export function Thread({ messages }: { messages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<
    Message[],
    string
  >(messages, (state, newMessage) => [...state, { message: newMessage }])

  return (
    <div>
      {optimisticMessages.map((m, k) => (
        <div key={k}>{m.message}</div>
      ))}
      <form
        action={async (formData: FormData) => {
          const message = formData.get('message')
          addOptimisticMessage(message)
          await send(message)
        }}
      >
        <input type="text" name="message" />
        <button type="submit">إرسال</button>
      </form>
    </div>
  )
}
'use client'

import { useOptimistic } from 'react'
import { send } from './actions'

export function Thread({ messages }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [...state, { message: newMessage }]
  )

  return (
    <div>
      {optimisticMessages.map((m) => (
        <div>{m.message}</div>
      ))}
      <form
        action={async (formData) => {
          const message = formData.get('message')
          addOptimisticMessage(message)
          await send(message)
        }}
      >
        <input type="text" name="message" />
        <button type="submit">إرسال</button>
      </form>
    </div>
  )
}

العناصر المتداخلة

يمكنك استدعاء إجراء خادم في عناصر متداخلة داخل <form> مثل <button>، و<input type="submit">، و<input type="image">. تقبل هذه العناصر خاصية formAction أو معالجات الأحداث.

هذا مفيد في الحالات التي ترغب فيها في استدعاء إجراءات خادم متعددة داخل نموذج. على سبيل المثال، يمكنك إنشاء عنصر <button> محدد لحفظ مسودة منشور بالإضافة إلى نشره. راجع وثائق React <form> لمزيد من المعلومات.

إرسال النموذج برمجيًا

يمكنك تشغيل إرسال النموذج باستخدام طريقة requestSubmit(). على سبيل المثال، عندما يضغط المستخدم على + Enter، يمكنك الاستماع لحدث onKeyDown:

'use client'

export function Entry() {
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (
      (e.ctrlKey || e.metaKey) &&
      (e.key === 'Enter' || e.key === 'NumpadEnter')
    ) {
      e.preventDefault()
      e.currentTarget.form?.requestSubmit()
    }
  }

  return (
    <div>
      <textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
    </div>
  )
}
'use client'

export function Entry() {
  const handleKeyDown = (e) => {
    if (
      (e.ctrlKey || e.metaKey) &&
      (e.key === 'Enter' || e.key === 'NumpadEnter')
    ) {
      e.preventDefault()
      e.currentTarget.form?.requestSubmit()
    }
  }

  return (
    <div>
      <textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
    </div>
  )
}

سيؤدي هذا إلى تشغيل إرسال أقرب عنصر <form> أبوي، مما سيستدعي إجراء الخادم (Server Action).

عناصر غير النماذج

بينما يُشاع استخدام إجراءات الخادم داخل عناصر <form>، إلا أنه يمكن أيضًا استدعاؤها من أجزاء أخرى من الكود مثل معالجات الأحداث و useEffect.

معالجات الأحداث

يمكنك استدعاء إجراء خادم من معالجات الأحداث مثل onClick. على سبيل المثال، لزيادة عدد الإعجابات:

'use client'

import { incrementLike } from './actions'
import { useState } from 'react'

export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)

  return (
    <>
      <p>Total Likes: {likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        Like
      </button>
    </>
  )
}
'use client'

import { incrementLike } from './actions'
import { useState } from 'react'

export default function LikeButton({ initialLikes }) {
  const [likes, setLikes] = useState(initialLikes)

  return (
    <>
      <p>Total Likes: {likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        Like
      </button>
    </>
  )
}

لتحسين تجربة المستخدم، نوصي باستخدام واجهات برمجة تطبيقات React الأخرى مثل useOptimistic و useTransition لتحديث واجهة المستخدم قبل انتهاء تنفيذ إجراء الخادم على الخادم، أو لإظهار حالة انتظار.

يمكنك أيضًا إضافة معالجات أحداث لعناصر النموذج، على سبيل المثال، لحفظ حقل نموذج عند onChange:

app/ui/edit-post.tsx
'use client'

import { publishPost, saveDraft } from './actions'

export default function EditPost() {
  return (
    <form action={publishPost}>
      <textarea
        name="content"
        onChange={async (e) => {
          await saveDraft(e.target.value)
        }}
      />
      <button type="submit">Publish</button>
    </form>
  )
}

في حالات مثل هذه، حيث قد يتم تشغيل أحداث متعددة بسرعة، نوصي باستخدام تقليل التردد (debouncing) لمنع استدعاءات إجراءات الخادم غير الضرورية.

useEffect

يمكنك استخدام خطاف React useEffect لاستدعاء إجراء خادم عند تركيب المكون أو تغيير تبعية. هذا مفيد للتحولات التي تعتمد على أحداث عالمية أو تحتاج إلى تشغيل تلقائي. على سبيل المثال، onKeyDown لاختصارات التطبيق، خطاف مراقبة التقاطع (intersection observer) للتمرير اللانهائي، أو عند تركيب المكون لتحديث عدد المشاهدات:

'use client'

import { incrementViews } from './actions'
import { useState, useEffect } from 'react'

export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)

  useEffect(() => {
    const updateViews = async () => {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    }

    updateViews()
  }, [])

  return <p>Total Views: {views}</p>
}
'use client'

import { incrementViews } from './actions'
import { useState, useEffect } from 'react'

export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)

  useEffect(() => {
    const updateViews = async () => {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    }

    updateViews()
  }, [])

  return <p>Total Views: {views}</p>
}

تذكر مراعاة سلوكيات ومحاذير useEffect.

معالجة الأخطاء

عند حدوث خطأ، سيتم التقاطه بواسطة error.js الأقرب أو حدود <Suspense> على العميل. نوصي باستخدام try/catch لإرجاع الأخطاء ليتم معالجتها بواسطة واجهة المستخدم الخاصة بك.

على سبيل المثال، قد يتعامل إجراء الخادم الخاص بك مع الأخطاء من إنشاء عنصر جديد عن طريق إرجاع رسالة:

'use server'

export async function createTodo(prevState: any, formData: FormData) {
  try {
    // Mutate data
  } catch (e) {
    throw new Error('Failed to create task')
  }
}
'use server'

export async function createTodo(prevState, formData) {
  try {
    //  Mutate data
  } catch (e) {
    throw new Error('Failed to create task')
  }
}

معلومة جيدة:

إعادة التحقق من البيانات

يمكنك إعادة التحقق من ذاكرة التخزين المؤقت لـ Next.js داخل إجراءات الخادم الخاصة بك باستخدام واجهة برمجة التطبيقات revalidatePath:

'use server'

import { revalidatePath } from 'next/cache'

export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidatePath('/posts')
}
'use server'

import { revalidatePath } from 'next/cache'

export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidatePath('/posts')
}

أو إبطال جلب بيانات محدد بعلامة ذاكرة تخزين مؤقت باستخدام revalidateTag:

'use server'

import { revalidateTag } from 'next/cache'

export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidateTag('posts')
}
'use server'

import { revalidateTag } from 'next/cache'

export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidateTag('posts')
}

إعادة التوجيه

إذا كنت ترغب في توجيه المستخدم إلى مسار مختلف بعد اكتمال إجراء الخادم، يمكنك استخدام واجهة برمجة التطبيقات redirect. يجب استدعاء redirect خارج كتلة try/catch:

'use server'

import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'

export async function createPost(id: string) {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidateTag('posts') // تحديث المنشورات المخزنة مؤقتًا
  redirect(`/post/${id}`) // الانتقال إلى صفحة المنشور الجديدة
}
'use server'

import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'

export async function createPost(id) {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidateTag('posts') // تحديث المنشورات المخزنة مؤقتًا
  redirect(`/post/${id}`) // الانتقال إلى صفحة المنشور الجديدة
}

ملفات تعريف الارتباط (Cookies)

يمكنك الحصول على، تعيين، وحذف ملفات تعريف الارتباط داخل إجراء خادم باستخدام واجهة برمجة التطبيقات cookies:

'use server'

import { cookies } from 'next/headers'

export async function exampleAction() {
  // الحصول على ملف تعريف الارتباط
  const value = cookies().get('name')?.value

  // تعيين ملف تعريف الارتباط
  cookies().set('name', 'Delba')

  // حذف ملف تعريف الارتباط
  cookies().delete('name')
}
'use server'

import { cookies } from 'next/headers'

export async function exampleAction() {
  // الحصول على ملف تعريف الارتباط
  const value = cookies().get('name')?.value

  // تعيين ملف تعريف الارتباط
  cookies().set('name', 'Delba')

  // حذف ملف تعريف الارتباط
  cookies().delete('name')
}

راجع أمثلة إضافية لحذف ملفات تعريف الارتباط من إجراءات الخادم.

الأمان

المصادقة والتفويض

يجب معاملة إجراءات الخادم كما لو كانت نقاط نهاية API عامة، والتأكد من أن المستخدم مخول لتنفيذ الإجراء. على سبيل المثال:

app/actions.ts
'use server'

import { auth } from './lib'

export function addItem() {
  const { user } = auth()
  if (!user) {
    throw new Error('You must be signed in to perform this action')
  }

  // ...
}

الإغلاقات والتشفير

يؤدي تعريف إجراء خادم داخل مكون إلى إنشاء إغلاق (closure) حيث يكون للإجراء حق الوصول إلى نطاق الوظيفة الخارجية. على سبيل المثال، لدى إجراء publish حق الوصول إلى المتغير publishVersion:

export default function Page() {
  const publishVersion = await getLatestVersion();

  async function publish(formData: FormData) {
    "use server";
    if (publishVersion !== await getLatestVersion()) {
      throw new Error('The version has changed since pressing publish');
    }
    ...
  }

  return <button action={publish}>Publish</button>;
}
export default function Page() {
  const publishVersion = await getLatestVersion();

  async function publish() {
    "use server";
    if (publishVersion !== await getLatestVersion()) {
      throw new Error('The version has changed since pressing publish');
    }
    ...
  }

  return <button action={publish}>Publish</button>;
}

تكون الإغلاقات مفيدة عندما تحتاج إلى التقاط لقطة للبيانات (مثل publishVersion) في وقت العرض بحيث يمكن استخدامها لاحقًا عند استدعاء الإجراء.

ومع ذلك، لحدوث هذا، يتم إرسال المتغيرات الملتقطة إلى العميل والعودة إلى الخادم عند استدعاء الإجراء. لمنع تعرض البيانات الحساسة للعميل، يقوم Next.js تلقائيًا بتشفير المتغيرات المغلقة. يتم إنشاء مفتاح خاص جديد لكل إجراء في كل مرة يتم فيها بناء تطبيق Next.js. هذا يعني أنه يمكن استدعاء الإجراءات فقط لبناء محدد.

معلومة جيدة: لا نوصي بالاعتماد على التشفير وحده لمنع تعرض القيم الحساسة للعميل. بدلاً من ذلك، يجب عليك استخدام واجهات برمجة تطبيقات React للتدنيس (taint APIs) لمنع بيانات معينة من إرسالها إلى العميل بشكل استباقي.

تجاوز مفاتيح التشفير (متقدم)

عند استضافة تطبيق Next.js الخاص بك على خوادم متعددة، قد ينتهي كل مثيل خادم بمفتاح تشفير مختلف، مما يؤدي إلى تناقضات محتملة.

لتخفيف هذا، يمكنك تجاوز مفتاح التشفير باستخدام متغير البيئة process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY. يضمن تحديد هذا المتغير أن مفاتيح التشفير الخاصة بك ثابتة عبر البناءات، وجميع مثيلات الخادم تستخدم نفس المفتاح.

هذه حالة استخدام متقدمة حيث يكون سلوك التشفير المتسق عبر عمليات النشر المتعددة أمرًا بالغ الأهمية لتطبيقك. يجب عليك مراعاة ممارسات الأمان القياسية مثل تدوير المفاتيح والتوقيع.

معلومة جيدة: يتم التعامل مع هذا تلقائيًا في تطبيقات Next.js الموزعة على Vercel.

الأصول المسموح بها (متقدم)

نظرًا لأنه يمكن استدعاء إجراءات الخادم في عنصر <form>، فهذا يعرضها لهجمات CSRF.

خلف الكواليس، تستخدم إجراءات الخادم طريقة POST، وهذه الطريقة HTTP الوحيدة المسموح بها لاستدعائها. هذا يمنع معظم نقاط الضعف CSRF في المتصفحات الحديثة، خاصة مع كون ملفات تعريف الارتباط SameSite هي الافتراضية.

كحماية إضافية، تقارن إجراءات الخادم في Next.js أيضًا رأس الأصل (Origin header) بـرأس المضيف (Host header) (أو X-Forwarded-Host). إذا لم تتطابق هذه، سيتم إحباط الطلب. بمعنى آخر، يمكن استدعاء إجراءات الخادم فقط على نفس المضيف الذي يستضيف الصفحة.

بالنسبة للتطبيقات الكبيرة التي تستخدم وكلاء عكسيين أو بنى خلفية متعددة الطبقات (حيث يختلف خادم API عن نطاق الإنتاج)، يُوصى باستخدام خيار التكوين serverActions.allowedOrigins لتحديد قائمة بالأصول الآمنة. يقبل الخيار مصفوفة من السلاسل.

next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
    },
  },
}

تعرف على المزيد حول الأمان وإجراءات الخادم.

موارد إضافية

لمزيد من المعلومات حول إجراءات الخادم، راجع وثائق React التالية: