Zod v4 مع Next.js 15: دليل شامل للتحقق من البيانات في النماذج وواجهات API و Server Actions

تحقق من كل شيء. لا تثق بأي مدخل. Zod v4 هي أسرع مكتبة للتحقق من المخططات في TypeScript، وتتكامل بشكل مثالي مع Next.js 15. في هذا الدليل، ستبني تطبيق إدارة جهات اتصال جاهز للإنتاج مع تحقق متين عبر النماذج وواجهات API و Server Actions ومتغيرات البيئة.
ما ستتعلمه
بنهاية هذا الدليل، ستكون قادرًا على:
- فهم ما تغير في Zod v4 ولماذا هو مهم
- تعريف مخططات قابلة لإعادة الاستخدام لتطبيق Next.js بالكامل
- التحقق من مدخلات Server Actions باستخدام Zod و
useActionState - تأمين واجهات API Route Handlers بالتحقق من جسم الطلب ومعاملات الاستعلام
- تحليل والتحقق من متغيرات البيئة عند بدء التشغيل
- التعامل مع أخطاء التحقق برسائل واضحة للمستخدم
- بناء نماذج آمنة الأنواع تشارك المخططات بين العميل والخادم
المتطلبات المسبقة
قبل البدء، تأكد من توفر:
- Node.js 20+ مثبت (
node --version) - خبرة في TypeScript (الأنواع، الأنواع العامة، الاستنتاج)
- معرفة بـ Next.js 15 (App Router، Server Components، Server Actions)
- أساسيات React 19 (
useActionState، إجراءات النماذج) - محرر أكواد — VS Code أو Cursor موصى به
لماذا Zod v4؟
كانت Zod المكتبة الأساسية للتحقق من المخططات في TypeScript منذ 2022. الإصدار الرابع هو إعادة كتابة كاملة تجلب تحسينات هائلة في الأداء وميزات جديدة:
| الميزة | Zod v3 | Zod v4 |
|---|---|---|
| سرعة التحليل | خط الأساس | أسرع 2-7 مرات |
| حجم الحزمة | حوالي 57 KB | حوالي 13 KB (أصغر بنسبة 77%) |
| Tree-shaking | محدود | دعم كامل لـ ESM |
| رسائل الخطأ | أساسية | غنية ومنظمة |
| JSON Schema | مكتبة خارجية | مدمج z.toJSONSchema() |
| Template literals | لا | z.templateLiteral() |
| البيانات الوصفية | لا | z.registry() للنماذج والتوثيق |
أكبر المكاسب هي السرعة والحجم. Zod v4 يحلل المخططات أسرع بـ 2-7 مرات من v3، وهذا مهم عند التحقق من كل طلب API وإرسال نموذج. تقليل الحزمة بنسبة 77% يعني JavaScript أقل يُرسل للعميل.
الخطوة 1: إعداد المشروع
أنشئ مشروع Next.js 15 جديد وثبّت Zod v4:
npx create-next-app@latest zod-nextjs-demo --typescript --tailwind --eslint --app --src-dir --turbopack
cd zod-nextjs-demoثبّت Zod v4:
npm install zod@^4تحقق من تثبيت Zod v4:
npx tsx -e "import {z} from 'zod'; console.log(z.version)"
# يجب أن يظهر 4.x.xهيكل المشروع سيبدو هكذا:
src/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── contacts/
│ │ ├── page.tsx
│ │ ├── new/
│ │ │ └── page.tsx
│ │ └── [id]/
│ │ └── page.tsx
│ └── api/
│ └── contacts/
│ └── route.ts
├── lib/
│ ├── schemas.ts # جميع مخططات Zod
│ ├── env.ts # التحقق من البيئة
│ └── actions.ts # Server Actions
└── components/
└── contact-form.tsx # مكون النموذج
الخطوة 2: تعريف المخططات
المبدأ الأساسي في Zod هو عرّف مرة واحدة، استخدم في كل مكان. أنشئ ملف مخططات مركزي يستورده كل من العميل والخادم.
أنشئ src/lib/schemas.ts:
import { z } from "zod";
// مخطط جهة الاتصال الأساسي — يُعاد استخدامه في النماذج وAPI والإجراءات
export const contactSchema = z.object({
name: z
.string()
.min(2, "الاسم يجب أن يكون حرفين على الأقل")
.max(100, "الاسم يجب أن يكون أقل من 100 حرف")
.trim(),
email: z
.string()
.email("يرجى إدخال بريد إلكتروني صالح")
.toLowerCase(),
phone: z
.string()
.regex(/^\+?[\d\s-()]{7,15}$/, "يرجى إدخال رقم هاتف صالح")
.optional()
.or(z.literal("")),
company: z
.string()
.max(200, "اسم الشركة طويل جدًا")
.optional(),
message: z
.string()
.min(10, "الرسالة يجب أن تكون 10 أحرف على الأقل")
.max(5000, "الرسالة يجب أن تكون أقل من 5000 حرف"),
priority: z.enum(["low", "medium", "high", "urgent"], {
message: "يرجى اختيار مستوى أولوية صالح",
}),
});
// استنتاج أنواع TypeScript من المخطط
export type Contact = z.infer<typeof contactSchema>;
// مخطط التحديث (جميع الحقول اختيارية ما عدا id)
export const contactUpdateSchema = contactSchema.partial().extend({
id: z.string().uuid("معرّف جهة الاتصال غير صالح"),
});
export type ContactUpdate = z.infer<typeof contactUpdateSchema>;
// مخطط معاملات البحث والتصفية
export const contactQuerySchema = z.object({
q: z.string().optional().default(""),
priority: z.enum(["low", "medium", "high", "urgent"]).optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(["name", "email", "createdAt"]).default("createdAt"),
order: z.enum(["asc", "desc"]).default("desc"),
});
export type ContactQuery = z.infer<typeof contactQuerySchema>;أنماط Zod v4 الأساسية المستخدمة
1. رسائل خطأ مخصصة مباشرة:
في Zod v4، تمرر رسائل الخطأ مباشرة كمعامل ثانٍ للمدققات:
z.string().min(2, "الاسم يجب أن يكون حرفين على الأقل")2. التحويل القسري لمعاملات الاستعلام:
z.coerce.number() يحول تلقائيًا معاملات الاستعلام النصية مثل "5" إلى الرقم 5. هذا ضروري لمعاملات URL التي تكون دائمًا نصوصًا.
3. استنتاج الأنواع مع z.infer:
لا تكتب واجهات TypeScript يدويًا أبدًا. مخططات Zod هي أنواعك:
// هذا النوع يُستنتج تلقائيًا:
// {
// name: string;
// email: string;
// phone?: string | undefined;
// company?: string | undefined;
// message: string;
// priority: "low" | "medium" | "high" | "urgent";
// }
export type Contact = z.infer<typeof contactSchema>;الخطوة 3: التحقق من متغيرات البيئة
من أكثر الاستخدامات تأثيرًا لـ Zod هو التحقق من متغيرات البيئة عند بدء التشغيل. اكتشف الأخطاء في الإعدادات قبل أن يخدم تطبيقك أي طلب.
أنشئ src/lib/env.ts:
import { z } from "zod";
const envSchema = z.object({
// قاعدة البيانات
DATABASE_URL: z.string().url("DATABASE_URL يجب أن يكون عنوان URL صالح"),
// المصادقة
AUTH_SECRET: z
.string()
.min(32, "AUTH_SECRET يجب أن يكون 32 حرفًا على الأقل"),
// التطبيق
NEXT_PUBLIC_APP_URL: z
.string()
.url()
.default("http://localhost:3000"),
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
// البريد الإلكتروني (اختياري في التطوير)
SMTP_HOST: z.string().optional(),
SMTP_PORT: z.coerce.number().default(587),
SMTP_USER: z.string().optional(),
SMTP_PASS: z.string().optional(),
});
// تحليل والتحقق — يطرح خطأ عند بدء التشغيل إذا كانت القيم غير صالحة
function validateEnv() {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error("متغيرات البيئة غير صالحة:");
console.error(result.error.format());
throw new Error("إعدادات البيئة غير صالحة");
}
return result.data;
}
export const env = validateEnv();
// وصول آمن الأنواع: env.DATABASE_URL هو string، env.SMTP_PORT هو numberالآن استورد env في أي مكان بدلاً من استخدام process.env مباشرة:
import { env } from "@/lib/env";
// آمن الأنواع، محقق منه، مع تطبيق القيم الافتراضية
const dbUrl = env.DATABASE_URL; // string (مضمون)
const port = env.SMTP_PORT; // number (محوّل من نص)لا تستورد env.ts أبدًا في مكونات العميل. يصل إلى process.env الموجود فقط على الخادم. لمتغيرات البيئة على جانب العميل، استخدم المتغيرات ذات البادئة NEXT_PUBLIC_ مباشرة.
الخطوة 4: Server Actions مع التحقق بـ Zod
Server Actions هي الطريقة الأساسية للتعامل مع إرسال النماذج في Next.js 15. Zod يجعلها آمنة الأنواع ومحمية.
أنشئ src/lib/actions.ts:
"use server";
import { contactSchema, type Contact } from "./schemas";
// نوع حالة الإجراء لـ useActionState
export type ActionState = {
success: boolean;
message: string;
errors?: Record<string, string[]>;
data?: Contact;
};
export async function createContact(
prevState: ActionState,
formData: FormData
): Promise<ActionState> {
// استخراج البيانات الخام من النموذج
const rawData = {
name: formData.get("name"),
email: formData.get("email"),
phone: formData.get("phone"),
company: formData.get("company"),
message: formData.get("message"),
priority: formData.get("priority"),
};
// التحقق باستخدام Zod
const result = contactSchema.safeParse(rawData);
if (!result.success) {
// تحويل أخطاء Zod إلى كائن مسطح للنموذج
const fieldErrors: Record<string, string[]> = {};
for (const issue of result.error.issues) {
const field = issue.path[0]?.toString() ?? "form";
if (!fieldErrors[field]) fieldErrors[field] = [];
fieldErrors[field].push(issue.message);
}
return {
success: false,
message: "يرجى تصحيح الأخطاء أدناه.",
errors: fieldErrors,
};
}
// result.data من نوع Contact بالكامل
const validatedData = result.data;
try {
// في تطبيق حقيقي، احفظ في قاعدة البيانات
console.log("إنشاء جهة اتصال:", validatedData);
// محاكاة إدخال قاعدة البيانات
await new Promise((resolve) => setTimeout(resolve, 500));
return {
success: true,
message: `تم إنشاء جهة الاتصال "${validatedData.name}" بنجاح!`,
data: validatedData,
};
} catch (error) {
return {
success: false,
message: "حدث خطأ ما. يرجى المحاولة مرة أخرى.",
};
}
}لماذا safeParse بدلاً من parse؟
parse()يطرح خطأZodErrorعند مدخل غير صالح — مثالي لمسارات API حيث تلتقط الخطأ وتعيد 400safeParse()يعيد{ success, data, error }— مثالي لـ Server Actions حيث تحتاج إعادة حالة الخطأ للنموذج
الخطوة 5: بناء النموذج المُحقق منه
أنشئ مكون نموذج يعرض أخطاء التحقق من الخادم باستخدام useActionState.
أنشئ src/components/contact-form.tsx:
"use client";
import { useActionState } from "react";
import { createContact, type ActionState } from "@/lib/actions";
const initialState: ActionState = {
success: false,
message: "",
};
export function ContactForm() {
const [state, formAction, isPending] = useActionState(
createContact,
initialState
);
return (
<form action={formAction} className="space-y-6 max-w-lg">
{/* رسالة الحالة */}
{state.message && (
<div
className={`p-4 rounded-lg ${
state.success
? "bg-green-50 text-green-800 border border-green-200"
: state.errors
? "bg-red-50 text-red-800 border border-red-200"
: ""
}`}
>
{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 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
{state.errors?.name && (
<p className="mt-1 text-sm text-red-600">{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 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
{state.errors?.email && (
<p className="mt-1 text-sm text-red-600">{state.errors.email[0]}</p>
)}
</div>
{/* الهاتف */}
<div>
<label htmlFor="phone" className="block text-sm font-medium mb-1">
الهاتف
</label>
<input
id="phone"
name="phone"
type="tel"
className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
{state.errors?.phone && (
<p className="mt-1 text-sm text-red-600">{state.errors.phone[0]}</p>
)}
</div>
{/* الشركة */}
<div>
<label htmlFor="company" className="block text-sm font-medium mb-1">
الشركة
</label>
<input
id="company"
name="company"
type="text"
className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
{state.errors?.company && (
<p className="mt-1 text-sm text-red-600">
{state.errors.company[0]}
</p>
)}
</div>
{/* الرسالة */}
<div>
<label htmlFor="message" className="block text-sm font-medium mb-1">
الرسالة *
</label>
<textarea
id="message"
name="message"
rows={4}
required
className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
{state.errors?.message && (
<p className="mt-1 text-sm text-red-600">
{state.errors.message[0]}
</p>
)}
</div>
{/* الأولوية */}
<div>
<label htmlFor="priority" className="block text-sm font-medium mb-1">
الأولوية *
</label>
<select
id="priority"
name="priority"
required
className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
>
<option value="">اختر الأولوية</option>
<option value="low">منخفضة</option>
<option value="medium">متوسطة</option>
<option value="high">عالية</option>
<option value="urgent">عاجلة</option>
</select>
{state.errors?.priority && (
<p className="mt-1 text-sm text-red-600">
{state.errors.priority[0]}
</p>
)}
</div>
{/* إرسال */}
<button
type="submit"
disabled={isPending}
className="w-full py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isPending ? "جارٍ الإرسال..." : "إنشاء جهة اتصال"}
</button>
</form>
);
}كيف يعمل هذا
- يملأ المستخدم النموذج وينقر إرسال
formActionيرسلFormDataإلى Server ActioncreateContact- Zod يتحقق من جميع الحقول على الخادم
- إذا فشل التحقق، تُعاد الأخطاء للمكون عبر
state.errors - كل حقل يعرض رسالة الخطأ الخاصة به
- إذا نجح التحقق، تُنشأ جهة الاتصال وتظهر رسالة نجاح
حالة isPending من useActionState تتعامل مع حالة التحميل تلقائيًا — لا حاجة لـ useState يدوي.
الخطوة 6: التحقق في واجهات API Route Handlers
لمسارات API، استخدم Zod للتحقق من أجسام الطلبات ومعاملات الاستعلام ومعاملات المسار.
أنشئ src/app/api/contacts/route.ts:
import { NextRequest, NextResponse } from "next/server";
import { contactSchema, contactQuerySchema } from "@/lib/schemas";
// GET /api/contacts?q=john&priority=high&page=1&limit=10
export async function GET(request: NextRequest) {
const searchParams = Object.fromEntries(
request.nextUrl.searchParams.entries()
);
// التحقق من معاملات الاستعلام
const result = contactQuerySchema.safeParse(searchParams);
if (!result.success) {
return NextResponse.json(
{
error: "معاملات استعلام غير صالحة",
details: result.error.issues.map((i) => ({
field: i.path.join("."),
message: i.message,
})),
},
{ status: 400 }
);
}
const { q, priority, page, limit, sort, order } = result.data;
// في تطبيق حقيقي، استعلم من قاعدة البيانات
console.log("جلب جهات الاتصال:", { q, priority, page, limit, sort, order });
return NextResponse.json({
contacts: [],
pagination: { page, limit, total: 0 },
});
}
// POST /api/contacts
export async function POST(request: NextRequest) {
let body: unknown;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: "جسم JSON غير صالح" },
{ status: 400 }
);
}
// التحقق من جسم الطلب
const result = contactSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: "فشل التحقق",
details: result.error.issues.map((i) => ({
field: i.path.join("."),
message: i.message,
})),
},
{ status: 400 }
);
}
const contact = result.data;
// في تطبيق حقيقي، أدخل في قاعدة البيانات
console.log("إنشاء جهة اتصال عبر API:", contact);
return NextResponse.json(
{ id: crypto.randomUUID(), ...contact },
{ status: 201 }
);
}مساعد تحقق قابل لإعادة الاستخدام
إذا كان لديك العديد من مسارات API، استخرج مساعدًا لتقليل التكرار:
// src/lib/validate.ts
import { z, type ZodType } from "zod";
import { NextResponse } from "next/server";
export function validateBody<T extends ZodType>(schema: T, data: unknown) {
const result = schema.safeParse(data);
if (!result.success) {
return {
success: false as const,
response: NextResponse.json(
{
error: "فشل التحقق",
details: result.error.issues.map((i) => ({
field: i.path.join("."),
message: i.message,
})),
},
{ status: 400 }
),
};
}
return {
success: true as const,
data: result.data as z.infer<T>,
};
}الخطوة 7: أنماط Zod v4 المتقدمة
النمط 1: الاتحادات المميزة (Discriminated Unions)
تعامل مع أنواع نماذج مختلفة بمخطط واحد:
const notificationSchema = z.discriminatedUnion("channel", [
z.object({
channel: z.literal("email"),
email: z.string().email(),
subject: z.string().min(1),
}),
z.object({
channel: z.literal("sms"),
phone: z.string().regex(/^\+[\d]{10,15}$/),
}),
z.object({
channel: z.literal("push"),
deviceToken: z.string().min(1),
title: z.string().min(1),
}),
]);
// TypeScript يعرف الشكل الدقيق بناءً على "channel"
type Notification = z.infer<typeof notificationSchema>;النمط 2: تركيب المخططات مع .pipe()
حوّل وتحقق على مراحل:
// تحليل نص مفصول بفواصل إلى مصفوفة محققة
const tagsSchema = z
.string()
.transform((val) => val.split(",").map((s) => s.trim()))
.pipe(z.array(z.string().min(1).max(50)).min(1).max(10));
tagsSchema.parse("react, nextjs, typescript");
// النتيجة: ["react", "nextjs", "typescript"]النمط 3: توليد JSON Schema في Zod v4
ولّد JSON Schema من مخططات Zod — مثالي لتوثيق API أو مواصفات OpenAPI:
import { z } from "zod";
const userSchema = z.object({
name: z.string().describe("الاسم الكامل للمستخدم"),
email: z.string().email().describe("البريد الإلكتروني الأساسي"),
age: z.number().int().min(18).describe("يجب أن يكون 18 عامًا أو أكبر"),
});
// مدمج في Zod v4 — لا حاجة لمكتبة خارجية
const jsonSchema = z.toJSONSchema(userSchema);
console.log(JSON.stringify(jsonSchema, null, 2));النمط 4: خريطة أخطاء مخصصة
خصص جميع رسائل الخطأ عالميًا لتطبيقك:
z.config({
customError: (issue) => {
if (issue.code === "too_small" && issue.minimum === 1) {
return { message: "هذا الحقل مطلوب" };
}
if (issue.code === "invalid_type" && issue.expected === "string") {
return { message: "يرجى إدخال نص" };
}
return { message: issue.message };
},
});النمط 5: البيانات الوصفية للنماذج مع z.registry()
يقدم Zod v4 السجلات لإرفاق بيانات وصفية بالمخططات — مفيد لتوليد واجهات النماذج تلقائيًا:
const formRegistry = z.registry<{
label: string;
placeholder?: string;
helpText?: string;
}>();
const nameField = z.string().min(2);
const emailField = z.string().email();
formRegistry.register(nameField, {
label: "الاسم الكامل",
placeholder: "أحمد محمد",
helpText: "أدخل اسمك الأول والأخير",
});
formRegistry.register(emailField, {
label: "البريد الإلكتروني",
placeholder: "ahmed@example.com",
});
// استرجاع البيانات الوصفية لعرض النموذج
const nameMeta = formRegistry.get(nameField);
// { label: "الاسم الكامل", placeholder: "أحمد محمد", helpText: "..." }الخطوة 8: أفضل ممارسات معالجة الأخطاء
تسطيح الأخطاء للنماذج
يوفر Zod v4 دالة .flatten() لأشكال أخطاء صديقة للنماذج:
const result = contactSchema.safeParse(badData);
if (!result.success) {
const flat = result.error.flatten();
// flat.formErrors — مصفوفة أخطاء المستوى الأعلى
// flat.fieldErrors — { name: string[], email: string[], ... }
console.log(flat.fieldErrors);
// {
// name: ["الاسم يجب أن يكون حرفين على الأقل"],
// email: ["يرجى إدخال بريد إلكتروني صالح"],
// }
}تنسيق الأخطاء لاستجابات API
استخدم .format() لهياكل أخطاء متداخلة:
const formatted = result.error.format();
// {
// name: { _errors: ["الاسم يجب أن يكون حرفين على الأقل"] },
// email: { _errors: ["يرجى إدخال بريد إلكتروني صالح"] },
// }الخطوة 9: التحقق على جانب العميل (تحسين اختياري)
بينما التحقق على الخادم هو مصدر الحقيقة، يمكنك إضافة التحقق على العميل لتغذية راجعة فورية. بما أن المخططات مشتركة، قواعد التحقق تبقى متزامنة تلقائيًا.
"use client";
import { useState } from "react";
import { contactSchema } from "@/lib/schemas";
import type { z } from "zod";
export function useFormValidation() {
const [errors, setErrors] = useState<Record<string, string[]>>({});
function validateField(name: string, value: unknown) {
const fieldSchema = contactSchema.shape[name as keyof typeof contactSchema.shape];
if (!fieldSchema) return;
const result = fieldSchema.safeParse(value);
setErrors((prev) => ({
...prev,
[name]: result.success ? [] : result.error.issues.map((i) => i.message),
}));
}
function clearErrors() {
setErrors({});
}
return { errors, validateField, clearErrors };
}استخدمه في نموذجك للتحقق الفوري عند مغادرة الحقل:
const { errors, validateField } = useFormValidation();
<input
name="email"
onBlur={(e) => validateField("email", e.target.value)}
/>
{errors.email?.length > 0 && (
<p className="text-red-600">{errors.email[0]}</p>
)}الخطوة 10: اختبار المخططات
المخططات هي دوال بحتة — هي أسهل جزء في تطبيقك للاختبار.
// __tests__/schemas.test.ts
import { describe, it, expect } from "vitest";
import { contactSchema, contactQuerySchema } from "@/lib/schemas";
describe("contactSchema", () => {
const validContact = {
name: "أحمد محمد",
email: "ahmed@example.com",
message: "هذه رسالة اختبار لنموذج الاتصال.",
priority: "medium" as const,
};
it("يقبل بيانات جهة اتصال صالحة", () => {
const result = contactSchema.safeParse(validContact);
expect(result.success).toBe(true);
});
it("يرفض الاسم الفارغ", () => {
const result = contactSchema.safeParse({ ...validContact, name: "" });
expect(result.success).toBe(false);
});
it("يرفض البريد الإلكتروني غير الصالح", () => {
const result = contactSchema.safeParse({
...validContact,
email: "ليس-بريد",
});
expect(result.success).toBe(false);
});
it("يقطع ويحول البريد للأحرف الصغيرة", () => {
const result = contactSchema.safeParse({
...validContact,
email: " AHMED@Example.COM ",
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.email).toBe("ahmed@example.com");
}
});
it("يقبل الهاتف الاختياري", () => {
const result = contactSchema.safeParse({
...validContact,
phone: "+216 55 123 456",
});
expect(result.success).toBe(true);
});
});
describe("contactQuerySchema", () => {
it("يطبق القيم الافتراضية للمعاملات المفقودة", () => {
const result = contactQuerySchema.safeParse({});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.page).toBe(1);
expect(result.data.limit).toBe(20);
expect(result.data.sort).toBe("createdAt");
expect(result.data.order).toBe("desc");
}
});
it("يحول الأرقام النصية", () => {
const result = contactQuerySchema.safeParse({
page: "3",
limit: "50",
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.page).toBe(3);
expect(result.data.limit).toBe(50);
}
});
});شغّل الاختبارات:
npx vitest run --reporter=verboseاستكشاف الأخطاء وإصلاحها
خطأ "Cannot find module 'zod'"
تأكد من تثبيت Zod v4 تحديدًا:
npm install zod@^4خطأ "Type 'ZodObject' is not assignable..."
Zod v4 لديه تصديرات أنواع مختلفة. إذا كنت تحدّث من v3، حدّث الاستيرادات:
// v3 (قديم)
import { ZodType, ZodSchema } from "zod";
// v4 (جديد) — استخدم z.ZodType مباشرة
import { z } from "zod";
type Schema = z.ZodType;بيانات النموذج تعيد null
FormData.get() يعيد string | File | null. Zod يتعامل مع null برفضه كنوع غير صالح، مما يعطي رسائل خطأ مناسبة. لا حاجة لفحوصات null يدوية.
الخطوات التالية
الآن بعد إتقان التحقق بـ Zod v4 في Next.js، فكر في:
- إضافة تكامل قاعدة بيانات — استخدم Zod مع Drizzle ORM لأمان أنواع شامل من النموذج إلى قاعدة البيانات
- بناء واجهات API آمنة الأنواع — ادمج Zod مع tRPC لطبقات API بأنواع كاملة
- تنفيذ المصادقة — تحقق من نماذج المصادقة مع Better Auth
- استكشاف سجلات Zod v4 — ابنِ واجهات نماذج مولّدة تلقائيًا من البيانات الوصفية
- توليد مواصفات OpenAPI — استخدم
z.toJSONSchema()لتوثيق واجهاتك تلقائيًا
الخلاصة
Zod v4 هو طبقة التحقق التي يحتاجها كل تطبيق Next.js. بتعريف المخططات مرة واحدة ومشاركتها بين العميل والخادم، تحصل على:
- أمان الأنواع — أنواع TypeScript مشتقة من المخططات، لا تخرج عن التزامن أبدًا
- الأمان — كل مدخل محقق منه قبل المعالجة
- تجربة المطور — IntelliSense، إكمال تلقائي، وفحوصات وقت الترجمة
- تجربة المستخدم — رسائل خطأ واضحة ومحددة لكل حقل
- الأداء — تحليل أسرع 2-7 مرات وحزمة أصغر بنسبة 77% من Zod v3
نمط "عرّف مرة واحدة، تحقق في كل مكان" يزيل فئات كاملة من الأخطاء. نماذجك وواجهات API و Server Actions ومتغيرات البيئة كلها تشترك في نفس مصدر الحقيقة. عندما تغير قاعدة تحقق، تتحدث في كل مكان تلقائيًا.
ابدأ بـ z.object() و safeParse(). هذا كل ما تحتاجه لجعل تطبيق Next.js الخاص بك محصنًا.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

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

بناء واجهة GraphQL آمنة الأنواع مع Next.js App Router و Yoga و Pothos
تعلم كيفية بناء واجهة GraphQL API آمنة الأنواع بالكامل باستخدام Next.js 15 App Router و GraphQL Yoga و Pothos schema builder. يغطي هذا الدليل العملي تصميم المخططات والاستعلامات والتحولات والمصادقة وعميل React باستخدام urql.

بناء واجهات برمجة تطبيقات آمنة الأنواع من البداية للنهاية مع tRPC و Next.js App Router
تعلم كيفية بناء واجهات برمجة تطبيقات آمنة الأنواع بالكامل مع tRPC و Next.js 15 App Router. يغطي هذا الدليل العملي إعداد الموجه والإجراءات والوسيط وتكامل React Query والاستدعاءات من جانب الخادم — كل ذلك دون كتابة أي مخطط API.