React 19 Server Actions و useActionState: الدليل الشامل للتعامل مع النماذج

نماذج بدون تعقيد. يُغيّر React 19 جذرياً طريقة تعاملنا مع النماذج — تتيح لك Server Actions و useActionState بناء نماذج آمنة تعمل حتى قبل تحميل JavaScript.
ماذا ستتعلم
- كيف تحلّ Server Actions محل مسارات API للتعامل مع النماذج
- استخدام
useActionStateلإدارة حالة النموذج مع حالات الانتظار - بناء نماذج متدرجة التحسين تعمل بدون JavaScript
- التحقق الآمن من البيانات على الخادم باستخدام Zod
- التحديثات التفاؤلية باستخدام
useOptimistic - أنماط عملية: نماذج متعددة الخطوات، رفع الملفات، ومعالجة الأخطاء
المتطلبات الأساسية
قبل البدء في هذا الدليل، تأكد من توفر:
- Node.js 20+ مُثبّت على جهازك
- معرفة أساسية بـ React و TypeScript
- إلمام بـ Next.js App Router (الصفحات، التخطيطات، مكونات الخادم)
- محرر أكواد مثل VS Code
لماذا تُغيّر Server Actions كل شيء
قبل React 19، كان التعامل مع النماذج يتطلب عادةً:
- إنشاء مسار API منفصل (
/api/submit-form) - استخدام
useStateلكل حقل في النموذج - كتابة معالجات
onChangeلكل حقل إدخال - إدارة حالات التحميل والخطأ والنجاح يدوياً
- استدعاء
fetch()أو مكتبة لإرسال البيانات
هذا كمّ كبير من الكود المتكرر لشيء أساسي كالنماذج. تُزيل Server Actions معظم هذا التعقيد بالسماح لك بتعريف دوال خادم يستدعيها React مباشرة من العميل — بدون مسارات API، بدون استدعاءات fetch يدوية، وبدون إدارة حالة منفصلة.
// قبل: الطريقة القديمة
'use client'
import { useState } from 'react'
export default function ContactForm() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setLoading(true)
try {
const res = await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify({ name, email }),
})
if (!res.ok) throw new Error('Failed')
} catch (err) {
setError('Something went wrong')
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit}>
{/* ... الكثير من حقول الإدخال المتحكم بها */}
</form>
)
}// بعد: مع Server Actions
import { submitContact } from './actions'
export default function ContactForm() {
return (
<form action={submitContact}>
<input name="name" required />
<input name="email" type="email" required />
<button type="submit">إرسال</button>
</form>
)
}النسخة الجديدة تعمل بدون JavaScript، ليس فيها حالة على جانب العميل، ودالة الخادم تتولى كل شيء.
الخطوة 1: إعداد المشروع
لنُنشئ مشروع Next.js 15 جديد مع React 19:
npx create-next-app@latest react19-forms --typescript --tailwind --app --src-dir
cd react19-formsتأكد من وجود React 19 في ملف package.json:
{
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "^15.0.0"
}
}ثبّت Zod للتحقق من البيانات على الخادم:
npm install zodالخطوة 2: أول Server Action لك
أنشئ ملفاً لإجراءات الخادم. التوجيه "use server" في أعلى الملف يُخبر React أن هذه الوحدة تعمل على الخادم فقط:
// src/app/actions.ts
'use server'
export async function createUser(formData: FormData) {
const name = formData.get('name') as string
const email = formData.get('email') as string
// هذا يعمل على الخادم — آمن للوصول لقواعد البيانات والمفاتيح السرية
console.log('Creating user:', { name, email })
// محاكاة إدخال في قاعدة البيانات
await new Promise((resolve) => setTimeout(resolve, 1000))
return { success: true, message: `User ${name} created!` }
}الآن استخدمه في نموذج. هذا مكون خادم — لا حاجة لـ "use client":
// src/app/page.tsx
import { createUser } from './actions'
export default function HomePage() {
return (
<main className="max-w-md mx-auto mt-20 p-6">
<h1 className="text-2xl font-bold mb-6">إنشاء حساب</h1>
<form action={createUser} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium mb-1">
الاسم
</label>
<input
id="name"
name="name"
type="text"
required
className="w-full border rounded-lg px-3 py-2"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
البريد الإلكتروني
</label>
<input
id="email"
name="email"
type="email"
required
className="w-full border rounded-lg px-3 py-2"
/>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700"
>
إنشاء المستخدم
</button>
</form>
</main>
)
}هذا النموذج يعمل حتى مع تعطيل JavaScript! المتصفح يُرسل النموذج بشكل طبيعي، و Next.js يتولى تنفيذ Server Action على الخادم.
الخطوة 3: إضافة useActionState للتغذية الراجعة
النموذج الأساسي يعمل، لكن لا توجد تغذية راجعة — لا حالة تحميل، لا رسائل خطأ، لا تأكيد نجاح. هنا يأتي دور useActionState.
useActionState هو خطّاف (hook) في React 19 يُدير دورة حياة إجراء النموذج:
const [state, formAction, isPending] = useActionState(action, initialState)state— القيمة الحالية المُرجعة من الإجراء (أخطاء، رسائل نجاح، إلخ)formAction— نسخة مُغلّفة من إجراءك لتمريرها إلى<form action={...}>isPending— قيمة منطقية تُشير إلى ما إذا كان الإجراء قيد التنفيذ
أولاً، حدّث Server Action ليُرجع حالة مُهيكلة:
// src/app/actions.ts
'use server'
export type FormState = {
success: boolean
message: string
errors?: {
name?: string[]
email?: string[]
}
}
export async function createUser(
prevState: FormState,
formData: FormData
): Promise<FormState> {
const name = formData.get('name') as string
const email = formData.get('email') as string
// التحقق الأساسي
const errors: FormState['errors'] = {}
if (!name || name.length < 2) {
errors.name = ['يجب أن يكون الاسم حرفين على الأقل']
}
if (!email || !email.includes('@')) {
errors.email = ['يرجى إدخال بريد إلكتروني صالح']
}
if (Object.keys(errors).length > 0) {
return { success: false, message: 'فشل التحقق', errors }
}
// محاكاة عملية قاعدة بيانات
await new Promise((resolve) => setTimeout(resolve, 1000))
return { success: true, message: `مرحباً، ${name}!` }
}لاحظ أن توقيع الدالة تغيّر — يُمرر useActionState الحالة السابقة كمعامل أول، و formData كمعامل ثانٍ. هذا يختلف عن Server Action العادي حيث formData هو المعامل الوحيد.
الآن أنشئ مكون العميل مع useActionState:
// src/app/create-user-form.tsx
'use client'
import { useActionState } from 'react'
import { createUser, type FormState } from './actions'
const initialState: FormState = {
success: false,
message: '',
}
export default function CreateUserForm() {
const [state, formAction, isPending] = useActionState(createUser, initialState)
return (
<form action={formAction} className="space-y-4">
{/* رسالة النجاح */}
{state.success && (
<div className="bg-green-50 border border-green-200 text-green-800 px-4 py-3 rounded-lg">
{state.message}
</div>
)}
{/* رسالة الخطأ العامة */}
{!state.success && state.message && (
<div className="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg">
{state.message}
</div>
)}
<div>
<label htmlFor="name" className="block text-sm font-medium mb-1">
الاسم
</label>
<input
id="name"
name="name"
type="text"
required
className="w-full border rounded-lg px-3 py-2"
aria-describedby="name-error"
/>
{state.errors?.name && (
<p id="name-error" className="text-red-600 text-sm mt-1">
{state.errors.name[0]}
</p>
)}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
البريد الإلكتروني
</label>
<input
id="email"
name="email"
type="email"
required
className="w-full border rounded-lg px-3 py-2"
aria-describedby="email-error"
/>
{state.errors?.email && (
<p id="email-error" className="text-red-600 text-sm mt-1">
{state.errors.email[0]}
</p>
)}
</div>
<button
type="submit"
disabled={isPending}
className="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isPending ? 'جارٍ الإنشاء...' : 'إنشاء المستخدم'}
</button>
</form>
)
}حدّث صفحتك لاستخدام المكون الجديد:
// src/app/page.tsx
import CreateUserForm from './create-user-form'
export default function HomePage() {
return (
<main className="max-w-md mx-auto mt-20 p-6">
<h1 className="text-2xl font-bold mb-6">إنشاء حساب</h1>
<CreateUserForm />
</main>
)
}الخطوة 4: التحقق الآمن مع Zod
التحقق بالنصوص الثابتة هش وسهل الكسر. لنستخدم Zod للتحقق القوي والآمن:
// src/lib/schemas.ts
import { z } from 'zod'
export const createUserSchema = z.object({
name: z
.string()
.min(2, 'يجب أن يكون الاسم حرفين على الأقل')
.max(50, 'يجب أن يكون الاسم أقل من 50 حرفاً'),
email: z
.string()
.email('يرجى إدخال بريد إلكتروني صالح'),
password: z
.string()
.min(8, 'يجب أن تكون كلمة المرور 8 أحرف على الأقل')
.regex(/[A-Z]/, 'يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل')
.regex(/[0-9]/, 'يجب أن تحتوي كلمة المرور على رقم واحد على الأقل'),
})
export type CreateUserInput = z.infer<typeof createUserSchema>حدّث Server Action لاستخدام Zod:
// src/app/actions.ts
'use server'
import { createUserSchema } from '@/lib/schemas'
export type FormState = {
success: boolean
message: string
errors?: Record<string, string[]>
}
export async function createUser(
prevState: FormState,
formData: FormData
): Promise<FormState> {
// التحليل والتحقق مع Zod
const result = createUserSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
password: formData.get('password'),
})
if (!result.success) {
return {
success: false,
message: 'يرجى تصحيح الأخطاء أدناه',
errors: result.error.flatten().fieldErrors as Record<string, string[]>,
}
}
// result.data الآن مُحدد النوع بالكامل كـ CreateUserInput
const { name, email, password } = result.data
try {
// محاكاة إدخال في قاعدة البيانات
await new Promise((resolve) => setTimeout(resolve, 1000))
// في الإنتاج: تشفير كلمة المرور، إدخال في قاعدة البيانات
console.log('Creating user:', { name, email })
return { success: true, message: `تم إنشاء الحساب لـ ${name}!` }
} catch (error) {
return { success: false, message: 'فشل إنشاء الحساب. يرجى المحاولة مرة أخرى.' }
}
}تحقق دائماً على الخادم، حتى لو كنت تتحقق أيضاً على العميل. يمكن تجاوز التحقق على العميل — التحقق على الخادم هو حدود الأمان الحقيقية.
الخطوة 5: التحديثات التفاؤلية مع useOptimistic
للإجراءات التي تريد فيها ردود فعل فورية (مثل تبديل المفضلة أو إرسال تعليق)، يوفر React 19 خطّاف useOptimistic:
// src/app/comments/comment-form.tsx
'use client'
import { useActionState, useOptimistic } from 'react'
import { addComment, type CommentState } from './actions'
type Comment = {
id: string
text: string
author: string
pending?: boolean
}
export default function CommentSection({
initialComments,
}: {
initialComments: Comment[]
}) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
initialComments,
(state: Comment[], newComment: Comment) => [
...state,
{ ...newComment, pending: true },
]
)
const initialState: CommentState = { success: false, message: '' }
const [state, formAction, isPending] = useActionState(
async (prevState: CommentState, formData: FormData) => {
// إضافة تعليق تفاؤلي فوراً
addOptimisticComment({
id: crypto.randomUUID(),
text: formData.get('text') as string,
author: 'أنت',
pending: true,
})
// ثم تشغيل إجراء الخادم الفعلي
return addComment(prevState, formData)
},
initialState
)
return (
<div className="space-y-4">
{/* قائمة التعليقات */}
<ul className="space-y-3">
{optimisticComments.map((comment) => (
<li
key={comment.id}
className={`p-3 rounded-lg border ${
comment.pending ? 'opacity-50 bg-gray-50' : 'bg-white'
}`}
>
<p className="font-medium">{comment.author}</p>
<p className="text-gray-600">{comment.text}</p>
{comment.pending && (
<span className="text-xs text-gray-400">جارٍ الإرسال...</span>
)}
</li>
))}
</ul>
{/* نموذج التعليق */}
<form action={formAction} className="flex gap-2">
<input
name="text"
placeholder="أضف تعليقاً..."
required
className="flex-1 border rounded-lg px-3 py-2"
/>
<button
type="submit"
disabled={isPending}
className="bg-blue-600 text-white px-4 py-2 rounded-lg disabled:opacity-50"
>
نشر
</button>
</form>
</div>
)
}يظهر التعليق فوراً في واجهة المستخدم بحالة "قيد الانتظار" بصرياً، ثم يتم تأكيده (أو التراجع عنه) عند استجابة الخادم.
الخطوة 6: نمط النموذج متعدد الخطوات
للنماذج المعقدة مثل تدفقات الإعداد الأولي، يمكنك دمج Server Actions مع حالة العميل لبناء معالجات متعددة الخطوات:
// src/app/onboarding/onboarding-form.tsx
'use client'
import { useState } from 'react'
import { useActionState } from 'react'
import { completeOnboarding, type OnboardingState } from './actions'
const steps = ['الملف الشخصي', 'التفضيلات', 'المراجعة'] as const
export default function OnboardingForm() {
const [currentStep, setCurrentStep] = useState(0)
const initialState: OnboardingState = {
success: false,
message: '',
step: 0,
}
const [state, formAction, isPending] = useActionState(
completeOnboarding,
initialState
)
return (
<div className="max-w-lg mx-auto">
{/* شريط التقدم */}
<div className="flex mb-8">
{steps.map((step, index) => (
<div
key={step}
className={`flex-1 text-center py-2 text-sm font-medium ${
index <= currentStep
? 'text-blue-600 border-b-2 border-blue-600'
: 'text-gray-400 border-b-2 border-gray-200'
}`}
>
{step}
</div>
))}
</div>
<form action={formAction}>
{/* حقل مخفي لتتبع الخطوة */}
<input type="hidden" name="step" value={currentStep} />
{/* الخطوة 1: الملف الشخصي */}
{currentStep === 0 && (
<div className="space-y-4">
<input name="fullName" placeholder="الاسم الكامل" required
className="w-full border rounded-lg px-3 py-2" />
<input name="company" placeholder="الشركة" required
className="w-full border rounded-lg px-3 py-2" />
</div>
)}
{/* الخطوة 2: التفضيلات */}
{currentStep === 1 && (
<div className="space-y-4">
<select name="role" className="w-full border rounded-lg px-3 py-2">
<option value="developer">مطوّر</option>
<option value="designer">مصمم</option>
<option value="manager">مدير منتج</option>
</select>
<select name="experience" className="w-full border rounded-lg px-3 py-2">
<option value="junior">مبتدئ (0-2 سنوات)</option>
<option value="mid">متوسط (2-5 سنوات)</option>
<option value="senior">متقدم (5+ سنوات)</option>
</select>
</div>
)}
{/* الخطوة 3: المراجعة */}
{currentStep === 2 && (
<div className="bg-gray-50 p-4 rounded-lg">
<p className="text-gray-600">
راجع معلوماتك وانقر على إرسال لإكمال الإعداد.
</p>
</div>
)}
{/* التنقل */}
<div className="flex justify-between mt-6">
<button
type="button"
onClick={() => setCurrentStep((s) => Math.max(0, s - 1))}
className={`px-4 py-2 rounded-lg border ${
currentStep === 0 ? 'invisible' : ''
}`}
>
السابق
</button>
{currentStep < steps.length - 1 ? (
<button
type="button"
onClick={() => setCurrentStep((s) => s + 1)}
className="px-4 py-2 bg-blue-600 text-white rounded-lg"
>
التالي
</button>
) : (
<button
type="submit"
disabled={isPending}
className="px-4 py-2 bg-green-600 text-white rounded-lg disabled:opacity-50"
>
{isPending ? 'جارٍ الإرسال...' : 'إكمال الإعداد'}
</button>
)}
</div>
</form>
</div>
)
}الخطوة 7: رفع الملفات مع Server Actions
تتعامل Server Actions مع رفع الملفات بشكل طبيعي عبر FormData:
// src/app/upload/actions.ts
'use server'
import { writeFile } from 'fs/promises'
import path from 'path'
export type UploadState = {
success: boolean
message: string
url?: string
}
export async function uploadAvatar(
prevState: UploadState,
formData: FormData
): Promise<UploadState> {
const file = formData.get('avatar') as File
if (!file || file.size === 0) {
return { success: false, message: 'يرجى اختيار ملف' }
}
// التحقق من نوع الملف
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
if (!allowedTypes.includes(file.type)) {
return { success: false, message: 'يُسمح فقط بصور JPEG و PNG و WebP' }
}
// التحقق من حجم الملف (الحد الأقصى 5 ميغابايت)
const maxSize = 5 * 1024 * 1024
if (file.size > maxSize) {
return { success: false, message: 'يجب أن يكون حجم الملف أقل من 5 ميغابايت' }
}
try {
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
const filename = `${Date.now()}-${file.name}`
const uploadPath = path.join(process.cwd(), 'public', 'uploads', filename)
await writeFile(uploadPath, buffer)
return {
success: true,
message: 'تم رفع الصورة بنجاح!',
url: `/uploads/${filename}`,
}
} catch (error) {
return { success: false, message: 'فشل الرفع. يرجى المحاولة مرة أخرى.' }
}
}// src/app/upload/avatar-form.tsx
'use client'
import { useActionState, useRef } from 'react'
import { uploadAvatar, type UploadState } from './actions'
const initialState: UploadState = { success: false, message: '' }
export default function AvatarUploadForm() {
const [state, formAction, isPending] = useActionState(uploadAvatar, initialState)
const formRef = useRef<HTMLFormElement>(null)
return (
<form ref={formRef} action={formAction} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2">صورة الملف الشخصي</label>
<input
name="avatar"
type="file"
accept="image/jpeg,image/png,image/webp"
required
className="block w-full text-sm file:mr-4 file:py-2 file:px-4
file:rounded-lg file:border-0 file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100"
/>
</div>
{state.message && (
<div className={`px-4 py-3 rounded-lg text-sm ${
state.success
? 'bg-green-50 text-green-800'
: 'bg-red-50 text-red-800'
}`}>
{state.message}
</div>
)}
{state.url && (
<img
src={state.url}
alt="الصورة المرفوعة"
className="w-24 h-24 rounded-full object-cover"
/>
)}
<button
type="submit"
disabled={isPending}
className="bg-blue-600 text-white px-4 py-2 rounded-lg disabled:opacity-50"
>
{isPending ? 'جارٍ الرفع...' : 'رفع الصورة'}
</button>
</form>
)
}الخطوة 8: نمط مساعد النماذج القابل لإعادة الاستخدام
مع نمو تطبيقك، ستحتاج لنمط قابل لإعادة الاستخدام. إليك مساعداً عاماً:
// src/lib/form-utils.ts
import { z } from 'zod'
export type ActionState<T = undefined> = {
success: boolean
message: string
errors?: Record<string, string[]>
data?: T
}
export function createFormAction<TSchema extends z.ZodObject<any>, TResult = void>(
schema: TSchema,
handler: (data: z.infer<TSchema>) => Promise<TResult>
) {
return async (
prevState: ActionState<TResult>,
formData: FormData
): Promise<ActionState<TResult>> => {
const raw = Object.fromEntries(formData.entries())
const result = schema.safeParse(raw)
if (!result.success) {
return {
success: false,
message: 'فشل التحقق',
errors: result.error.flatten().fieldErrors as Record<string, string[]>,
}
}
try {
const data = await handler(result.data)
return { success: true, message: 'تم بنجاح', data: data as TResult }
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'حدث خطأ ما',
}
}
}
}الآن إنشاء إجراء نموذج جديد يصبح بسيطاً للغاية:
// src/app/actions.ts
'use server'
import { createFormAction } from '@/lib/form-utils'
import { createUserSchema } from '@/lib/schemas'
export const createUser = createFormAction(createUserSchema, async (data) => {
// data مُحدد النوع بالكامل كـ { name: string, email: string, password: string }
await db.user.create({ data })
return { id: crypto.randomUUID() }
})اختبار التطبيق
شغّل خادم التطوير:
npm run devاختبر السيناريوهات التالية:
- المسار الصحيح — املأ جميع الحقول بشكل صحيح وأرسل النموذج. يجب أن تظهر رسالة نجاح.
- أخطاء التحقق — أرسل ببريد إلكتروني غير صالح أو كلمة مرور قصيرة. يجب أن تظهر أخطاء على مستوى الحقول.
- JavaScript معطّل — عطّل JS في أدوات المطور وأرسل النموذج. يجب أن يعمل عبر إعادة تحميل كاملة للصفحة.
- حالة التحميل — انقر على إرسال ولاحظ الزر المعطّل مع نص "جارٍ الإنشاء...".
- أخطاء الشبكة — حاكِ فشل الخادم وتحقق من ظهور رسالة الخطأ.
استكشاف الأخطاء وإصلاحها
"Functions cannot be passed directly to Client Components"
هذا الخطأ يعني أنك تحاول تمرير Server Action كخاصية (prop) لمكون عميل بشكل غير صحيح. تأكد من:
- أن Server Action مُعرّف في ملف
'use server' - أنك تستورده مباشرة في مكون العميل، أو تمرره عبر خاصية
actionللنموذج
"useActionState is not a function"
تأكد أنك تستخدم React 19 أو أحدث. تحقق من package.json — إذا رأيت React 18.x، قم بالترقية:
npm install react@latest react-dom@latestبيانات النموذج فارغة
تأكد أن كل حقل إدخال يحتوي على خاصية name. يستخدم FormData خاصية name لجمع القيم — بدونها يكون الحقل غير مرئي للخادم.
النقاط الرئيسية
| النمط | متى يُستخدم |
|---|---|
action={serverAction} بسيط | نماذج بسيطة في مكونات الخادم، لا حاجة لتغذية راجعة |
useActionState | نماذج تحتاج حالات تحميل وأخطاء ورسائل نجاح |
useOptimistic | تغذية راجعة فورية للإجراءات (تعليقات، إعجابات، تبديلات) |
مساعد createFormAction | نمط قابل لإعادة الاستخدام للإجراءات المُتحقق منها عبر التطبيق |
الخطوات التالية
- استكشف revalidatePath و revalidateTag لتحديث البيانات المُخزنة مؤقتاً بعد التعديلات
- ابنِ تطبيق CRUD كامل يجمع Server Actions مع Prisma أو Drizzle ORM
- أضف تحديد معدل الطلبات (rate limiting) لإجراءات الخادم في بيئة الإنتاج
- ادمج مع مكتبة next-safe-action لمزيد من أمان الأنواع
الخلاصة
تُمثّل Server Actions و useActionState في React 19 تبسيطاً جوهرياً في طريقة بناء النماذج. بنقل منطق التحقق والتعديل إلى الخادم، تحصل على تحسين متدرج مجاناً، وتُزيل فئات كاملة من أخطاء جانب العميل، وتكتب كوداً أقل بكثير. الأنماط في هذا الدليل — من الإجراءات الأساسية إلى التحديثات التفاؤلية إلى المساعدات القابلة لإعادة الاستخدام — تمنحك أساساً متيناً لبناء نماذج جاهزة للإنتاج في أي تطبيق Next.js.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

Zod v4 مع Next.js 15: دليل شامل للتحقق من البيانات في النماذج وواجهات API و Server Actions
تعلم استخدام Zod v4 في Next.js 15 — تحقق من النماذج باستخدام Server Actions، أمّن مسارات API، تحقق من متغيرات البيئة، وابنِ تطبيقات آمنة الأنواع بالكامل مع أسرع مكتبة للتحقق في TypeScript.

بناء تطبيق متكامل باستخدام Drizzle ORM و Next.js 15: قاعدة بيانات آمنة الأنواع من الصفر إلى الإنتاج
تعلّم كيفية بناء تطبيق متكامل آمن الأنواع باستخدام Drizzle ORM مع Next.js 15. يغطي هذا الدليل العملي تصميم المخططات والترحيلات وServer Actions وعمليات CRUD والنشر مع PostgreSQL.

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.