Next.js: تحسين الصور والخطوط

آخر تحديث: 2026-08-26

الصور والخطوط تشكل أكثر من 70% من حجم الصفحة — تحسينها يمكن أن يقلل LCP من 4 ثوان إلى ثانية واحدة.

1. ما ستتعلمه



2. قصة حقيقية لمهندس أداء واجهات أمامية

(1) نقطة الألم: صورة واحدة يمكن أن تدمر أداء الصفحة بأكملها

Diana هي مهندسة DevOps في فريق TaskFlow. اكتشفت أن LCP (Largest Contentful Paint) لصفحة Dashboard يصل إلى 4.8 ثانية:

"البانر في أعلى الصفحة هو صورة PNG بحجم 5MB (الحجم الأصلي 4000×3000px)، يتم تحميلها مباشرة <img src="/banner.png" />. Chrome DevTools يظهر: فك الترميز استغرق 800 مللي ثانية، Cumulative Layout Shift (CLS) كان 0.45، وتحميل الخط حجب العرض لمدة 600 مللي ثانية."

المشكلة التأثير مقياس CWV
الصورة الأصلية 5MB غير مضغوطة تحميل 3.2 ثانية ❌ LCP 4.8 ثانية
لا توجد خصائص أبعاد تخطيط الصفحة يستمر في التحرك ❌ CLS 0.45
حجب عرض الخط شاشة بيضاء ممتدة ❌ FCP 2.1 ثانية
صور غير متجاوبة صور 4K تُحمل على الأجهزة المحمولة أيضاً ❌ إهدار البيانات

(2) حل next/image + next/font

استخدم مكون <Image> لتحسين الصور تلقائياً، و next/font لإزالة حجب الخط.

TSX
import Image from 'next/image'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'], display: 'swap' })

export default function Hero() {
  return (
    <div className={inter.className}>
      <Image
        src="/banner.webp"
        alt="TaskFlow Banner"
        width={1200}
        height={400}
        priority
        placeholder="blur"
        blurDataURL="data:image/webp;base64,..."
      />
    </div>
  )
}

(3) العائد

المقياس قبل التحسين بعد التحسين التحسن
LCP 4.8 ثانية 1.2 ثانية 75% ↓
CLS 0.45 0.02 96% ↓
حجم الصورة 5 MB 120 KB 97% ↓
FCP 2.1 ثانية 0.8 ثانية 62% ↓


3. السمات الأساسية لمكون next/image

(1) جدول مرجعي للسمات

السمة النوع مطلوبة الوصف
src string / StaticImport مسار الصورة أو استيراد ثابت
width number ✅ (ثابت) عرض الصورة (px)
height number ✅ (ثابت) ارتفاع الصورة (px)
alt string نص بديل (إمكانية الوصول)
priority boolean تحميل مسبق لصورة LCP
placeholder 'blur' / 'empty' عنصر نائب للتحميل
blurDataURL string يتطلب blur عنصر نائب ضبابي base64
sizes string نقاط التوقف المتجاوبة
fill boolean ملء الحاوية الأصل
quality number جودة الضغط (1–100)
loading 'lazy' / 'eager' استراتيجية التحميل الكسول
100%
graph TB
    A[<Image src="/photo.jpg"/>] --> B{Next.js Build}
    B --> C[إعادة ترميز Sharp من جانب الخادم]
    C --> D[نسخة WebP<br/>1200w / 800w / 400w]
    C --> E[نسخة AVIF<br/>1200w / 800w / 400w]
    D --> F[اختيار المتصفح<br/><picture> تعديل تلقائي]
    E --> F

    style B fill:#cce5ff
    style C fill:#d4edda
    style F fill:#fff3cd

(2) مثال على التكوين المتجاوب

TS
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'images.unsplash.com' },
      { protocol: 'https', hostname: 'cdn.taskflow.io', port: '', pathname: '/assets/**' }
    ],
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 1080, 1200, 1920],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384]
  }
}

export default nextConfig
⚠️ ملاحظة: images.domains تم إهماله في Next.js 15 وما بعده؛ يرجى استخدام remotePatterns (الذي يدعم مطابقة مسار بنمط wildcard).

▶ مثال: صورة بعيدة + متجاوبة + عنصر نائب ضبابي

المخرجات:

TEXT 📖 للعرض فقط
تم تنفيذ وحدة TypeScript بنجاح.
TSX
// components/TeamPhoto.tsx
import Image from 'next/image'

export default function TeamPhoto() {
  return (
    <Image
      src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=1200"
      alt="صورة فريق TaskFlow"
      width={1200}
      height={600}
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 1200px"
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
      priority
      className="rounded-lg"
    />
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يُعرض واجهة مكون TeamPhoto.
💡 نصيحة: sizes يخبر المتصفح بعرض عرض الصورة تحت أحجام منفذ عرض مختلفة، مما يساعد المتصفح على اختيار حجم الصورة الأنسب. إذا لم يتم تعيين هذا، قد ينتهي الأمر بالأجهزة المحمولة بتحميل صورة 1920px أيضاً.



4. placeholder="blur" و blurDataURL

(1) مخططان للعنصر النائب

الحل طريقة التوليد الحجم السيناريوهات المناسبة
استيراد ثابت لـ "blur" يُولد تلقائياً بواسطة Next.js ~2KB الصور المحلية (import img from './photo.jpg')
blurDataURL يدوي base64 مُولد بأداة ~200B الصور البعيدة، الصور المتحركة
مكتبة العنصر النائب يُولد في وقت التشغيل ~500B يتطلب URL بعيد ديناميكي

(2) الاستيراد الثابت ينتج ضبابية تلقائية

TSX
// ✅ استيراد ثابت — Next.js يُولد blurDataURL تلقائياً
import teamPhoto from '@/public/team.jpg'

export default function AboutPage() {
  return (
    <Image
      src={teamPhoto}
      alt="صورة الفريق"
      placeholder="blur"          // استخدام الضبابية المُولدة تلقائياً
      className="rounded-xl"
    />
  )
}

(3) أداة لتوليد معاينات ضبابية للصور البعيدة

TEXT 📖 للعرض فقط
# الاستخدام: مكتبة plaiceholder
npm install plaiceholder
TS
// lib/getBlurData.ts — توليد blurURL للصور البعيدة
import { getPlaiceholder } from 'plaiceholder'

export async function getBlurDataURL(src: string) {
  try {
    const response = await fetch(src)
    const buffer = Buffer.from(await response.arrayBuffer())
    const { base64 } = await getPlaiceholder(buffer)
    return base64
  } catch {
    return undefined
  }
}

▶ مثال: صورة بعيدة ديناميكية + عنصر نائب ضبابي

المخرجات:

TEXT 📖 للعرض فقط
تم تنفيذ كود TypeScript بنجاح.
TSX
// app/team/page.tsx — قائمة صور الفريق الرمزية + عنصر نائب blur
import Image from 'next/image'
import { getBlurDataURL } from '@/lib/getBlurData'

const members = [
  { name: 'Alice', avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200' },
  { name: 'Bob', avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=200' },
]

export default async function TeamPage() {
  const membersWithBlur = await Promise.all(
    members.map(async (m) => ({ ...m, blur: await getBlurDataURL(m.avatar) }))
  )

  return (
    <div style={{ display: 'flex', gap: '1rem' }}>
      {membersWithBlur.map((m) => (
        <Image
          key={m.name}
          src={m.avatar}
          alt={m.name}
          width={100}
          height={100}
          placeholder="blur"
          blurDataURL={m.blur}
          className="rounded-full"
        />
      ))}
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يُعرض قائمة بتمرير membersWithBlur، مع عرض كل m.


5. تحسين الخطوط باستخدام next/font

(1) تأثير تحميل الخط على الأداء

100%
graph TB
    A[@font-face التقليدي] --> B[حجب العرض<br/>FOIT]
    A --> C[إزاحة التخطيط<br/>FOUT]
    B --> D[تأخير FCP 300-600 مللي ثانية]
    C --> E[CLS 0.1-0.3]

    F[next/font] --> G[display:swap<br/>استخدام الخط الاحتياطي فوراً]
    F --> H[size-adjust<br/>إزالة إزاحات التخطيط]
    F --> I[تحميل مسبق preload<br/>المسار الحرج دون حجب]
    F --> J[CSS size-adjust<br/>تداخل مقاييس الخط]

    style A fill:#f8d7da
    style F fill:#d4edda
المشكلة تحميل الخط التقليدي حل next/font
FOIT (الخط غير مرئي) لا يُعرض النص حتى يتم تحميل الخط display:swap عرض الخط الاحتياطي فوراً
CLS (إزاحة التخطيط التراكمية) تغير الحجم قبل وبعد تحميل الخط size-adjust يتجاوز مقاييس الخط
طلبات إضافية تحميل تسلسلي لملفات خطوط متعددة CSS مضمن + تحميل مسبق
تأخير Google Fonts طلبات CDN بطيئة عبر الحدود يُحمل أثناء البناء، صفر طلبات وقت التشغيل

(2) خطوط Google المتغيرة

TSX
// app/layout.tsx — خطوط Google المتغيرة
import { Inter, Roboto_Mono } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  preload: true,
  variable: '--font-inter',         // أنماط متغيرات CSS
  weight: 'variable'                // نطاق الخط المتغير
})

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono'
})

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ar" className={`${inter.variable} ${robotoMono.variable}`}>
      <body style={{ fontFamily: 'var(--font-inter)' }}>
        <code style={{ fontFamily: 'var(--font-roboto-mono)' }}>{children}</code>
      </body>
    </html>
  )
}

(3) تعديل حجم الخط (size-adjust)

TSX
// خطوط محلية مخصصة + size-adjust لمنع CLS
import localFont from 'next/font/local'

const myFont = localFont({
  src: './fonts/CustomFont.woff2',
  display: 'swap',
  adjustment: {
    ascent: 90,
    descent: 20,
    lineGap: 10,
    sizeAdjust: '105%'
  }
})

▶ مثال: دمج خطوط متعددة + Tailwind CSS

المخرجات:

TEXT 📖 للعرض فقط
يُعرض واجهة مكون Component.
TSX
// app/layout.tsx — خطوط الأعمال + خط الكود + Tailwind
import { Inter, JetBrains_Mono } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  variable: '--font-sans',
  display: 'swap'
})

const jetbrainsMono = JetBrains_Mono({
  subsets: ['latin'],
  variable: '--font-mono',
  display: 'swap'
})

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ar" className={`${inter.variable} ${jetbrainsMono.variable}`}>
      <body className="font-sans">{children}</body>
    </html>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يُعرض واجهة مكون RootLayout.
CSS
/* tailwind.config.ts */
import type { Config } from 'tailwindcss'

export default {
  theme: {
    extend: {
      fontFamily: {
        sans: ['var(--font-sans)'],
        mono: ['var(--font-mono)']
      }
    }
  }
} satisfies Config


6. مبادئ تحسين الصور: Sharp + WebP/AVIF

(1) خط أنابيب تحسين Sharp

100%
graph LR
    A[الصورة المصدر<br/>PNG/JPEG 5MB] --> B[معالجة Sharp من جانب الخادم]
    B --> C[تغيير الحجم<br/>1200w / 800w / 400w]
    B --> D[ضغط الجودة<br/>جودة 75%]
    B --> E[تحويل التنسيق]
    E --> F[WebP<br/>~80% تقليل الحجم↓]
    E --> G[AVIF<br/>~90% تقليل الحجم↓]
    F --> H[<picture> تفاوض تلقائي<br/>المتصفح: اختيار أفضل تنسيق]

    style B fill:#d4edda
    style H fill:#cce5ff
التنسيق نسبة الضغط (مقابل JPEG) دعم المتصفح سرعة فك الترميز
JPEG المعيار 100% الأسرع
WebP ~30% حجم ملف أقل 96% سريع إلى حد ما
AVIF ~50% تقليل حجم الملف 93% أبطأ
HEIC ~50% حجم ملف أقل Safari فقط أسرع

(2) تكوين جودة الصورة الشاملة

TS
// next.config.ts — إعدادات تحسين الصورة الشاملة
const nextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 1080, 1200, 1920],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 60 * 60 * 24 * 30,  // 30 يوماً في الذاكرة المؤقتة
    dangerouslyAllowSVG: false,
    contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;"
  }
}

▶ مثال: اختبار جودة الصورة وحجمها

المخرجات:

TEXT 📖 للعرض فقط
تم تنفيذ وحدة TypeScript بنجاح.
TSX
// components/ImageTest.tsx — مقارنة مستويات الجودة المختلفة لنفس الصورة
import Image from 'next/image'

export default function ImageTest() {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem' }}>
      <div>
        <h3>الجودة=50</h3>
        <Image src="/test.jpg" alt="q50" width={400} height={300} quality={50} />
      </div>
      <div>
        <h3>الجودة=75 (الافتراضي)</h3>
        <Image src="/test.jpg" alt="q75" width={400} height={300} quality={75} />
      </div>
      <div>
        <h3>الجودة=100</h3>
        <Image src="/test.jpg" alt="q100" width={400} height={300} quality={100} />
      </div>
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يُعرض: الجودة=50
نص مرئي: الجودة=50 | الجودة=75 (الافتراضي) | الجودة=100
💡 نصيحة: الفرق بين 75% و 100% غير محسوس تقريباً بالعين المجردة، لكن الحجم يختلف بمعامل 3 إلى 4. لبيئات الإنتاج، يوصى بـ 75–85%.



7. التحسين العملي لـ Core Web Vitals

(1) المقاييس الرئيسية الثلاثة لـ CWV

المقياس الحد الأقصى للدرجة الفجوة إجراءات التحسين
LCP (Largest Contentful Paint) ≤2.5 ثانية >4.0 ثانية تحميل مسبق لصور LCP + ضغط + CDN
CLS (Cumulative Layout Shift) ≤0.1 >0.25 أبعاد صورة ثابتة + تعديل حجم الخط
INP (Interaction to Next Paint) ≤200 مللي ثانية >500 مللي ثانية تقليل تنفيذ JS + تقسيم الكود

(2) قائمة تحقق تحسين صورة LCP

TSX
// ✅ إعدادات صورة LCP الصحيحة
export default function HeroBanner() {
  return (
    <Image
      src="/hero-banner.webp"
      alt="TaskFlow Hero"
      width={1440}
      height={600}
      priority          // إخبار Next.js بتحميل هذه الصورة مسبقاً
      quality={85}
      placeholder="blur"
      sizes="100vw"
      style={{ width: '100%', height: 'auto' }}  // متجاوب
    />
  )
}

(3) إزالة CLS الناتج عن الخط

TSX
// layout.tsx — حل كامل لإزالة CLS الناتج عن الخط
import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  adjustFontFallback: true,   // Next.js يحسب size-adjust تلقائياً
  fallback: ['system-ui', 'sans-serif']
})
🔥 خطأ شائع: لا تقم بتحميل أكثر من 3 أوزان خط في نفس الصفحة. كل متغير يضيف حوالي 50 KB إلى ملف WOFF2.

▶ مثال: مكون مراقبة CWV

المخرجات:

TEXT 📖 للعرض فقط
يُعرض واجهة مكون Component.
TSX
// components/WebVitals.tsx — إرسال CWV إلى منصة التحليلات
'use client'
import { useReportWebVitals } from 'next/web-vitals'

export default function WebVitalsReporter() {
  useReportWebVitals((metric) => {
    console.log(metric)  // عرض أثناء التطوير

    // الإبلاغ من بيئة الإنتاج إلى Analytics API
    if (process.env.NODE_ENV === 'production') {
      const body = JSON.stringify({
        name: metric.name,
        value: metric.value,
        rating: metric.rating,
        id: metric.id
      })
      navigator.sendBeacon('/api/analytics', body)
    }
  })

  return null
}

المخرجات:

TEXT 📖 للعرض فقط
يُعرض واجهة مكون WebVitalsReporter.
TSX
// app/layout.tsx — تركيب المراقبة
import WebVitalsReporter from '@/components/WebVitals'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <WebVitalsReporter />
      </body>
    </html>
  )
}


8. مثال كامل: تحسين شامل لصور الصفحة الرئيسية والخطوط و CWV

TSX
// app/layout.tsx — التخطيط الجذري، الخطوط الشاملة + الصور + مراقبة Vitals
import { Inter, Noto_Sans_SC } from 'next/font/google'
import WebVitalsReporter from '@/components/WebVitals'
import type { Metadata } from 'next'

const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
  display: 'swap',
  adjustFontFallback: true
})

const notoSansSC = Noto_Sans_SC({
  subsets: ['latin'],
  weight: 'variable',
  variable: '--font-noto',
  display: 'swap',
  adjustFontFallback: true
})

export const metadata: Metadata = {
  title: 'TaskFlow - منصة تعاون الفريق',
  description: 'TaskFlow تساعد أكثر من 10,000 فريق عالي الفعالية على التعاون'
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ar" className={`${inter.variable} ${notoSansSC.variable}`}>
      <body style={{ fontFamily: 'var(--font-inter), var(--font-noto), sans-serif' }}>
        {children}
        <WebVitalsReporter />
      </body>
    </html>
  )
}
TSX
// app/page.tsx — الصفحة الرئيسية Hero + تحسين LCP
import Image from 'next/image'
import Link from 'next/link'

export default function HomePage() {
  return (
    <div>
      {/* منطقة Hero — صورة LCP */}
      <div style={{ position: 'relative', width: '100%', height: 500 }}>
        <Image
          src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=1920&q=85"
          alt="تعاون فريق TaskFlow"
          fill
          priority
          sizes="100vw"
          style={{ objectFit: 'cover' }}
          placeholder="blur"
          blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
        />
        <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <div style={{ textAlign: 'center', color: '#fff' }}>
            <h1 style={{ fontSize: 'clamp(2rem, 5vw, 4rem)' }}>TaskFlow</h1>
            <p style={{ fontSize: '1.25rem' }}>أكثر من 10,000 فريق يستخدمون منصة التعاون</p>
            <Link href="/signup" style={{ display: 'inline-block', padding: '0.75rem 2rem', background: '#4f46e5', color: '#fff', borderRadius: 8 }}>
              ابدأ مجاناً
            </Link>
          </div>
        </div>
      </div>

      {/* منطقة الميزات — تحميل كسول للصور */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '2rem', padding: '4rem 2rem' }}>
        {features.map((feature) => (
          <div key={feature.title}>
            <Image
              src={feature.icon}
              alt={feature.title}
              width={48}
              height={48}
              loading="lazy"
            />
            <h3>{feature.title}</h3>
            <p>{feature.description}</p>
          </div>
        ))}
      </div>
    </div>
  )
}

const features = [
  { title: 'إدارة المشاريع', description: 'لوحة إعلانات + مخطط جانت', icon: '/icons/project.svg' },
  { title: 'تعاون فوري', description: 'تحرير متزامن من قبل عدة مستخدمين', icon: '/icons/team.svg' },
  { title: 'تحليل ذكي', description: 'مدعوم برؤى AI', icon: '/icons/analytics.svg' },
]

❓ أسئلة شائعة

س ما الفرق بين next/image و fill في width/height؟
ج fill يجعل الصورة تملأ حاويتها الأصل (التي يجب أن يكون لديها position: relative) دون تحديد حجم ثابت؛ يُستخدم بالتزامن مع objectFit. العرض والارتفاع الثابتان يتطلبان تحديد أبعاد الصورة صراحة لحساب المساحة التي تشغلها ومنع CLS.
س كيف أُولد صورة ضبابية لـ placeholder="blur"؟
ج Next.js يُولدها تلقائياً أثناء الاستيراد الثابت. للصور البعيدة، تحتاج إلى توفير blurDataURL يدوياً (صورة مصغرة ضبابية مشفرة بـ base64)، والتي يمكن توليدها باستخدام مكتبة plaiceholder أو أداة عبر الإنترنت (مثل https://png-pixel.com لتوليد صورة ضبابية 4×4).
س هل display=swap و next/font في Google Fonts هما نفس display:swap؟
ج لهما نفس التأثير لكن التنفيذ مختلف. الطريقة التقليدية تتضمن إضافة &display=swap إلى وسم <link> واستبداله فقط بعد انتهاء تحميل الخط. next/font يُحمل ملف الخط أثناء وقت البناء ويضمّنه في الصفحة عبر CSS، مما ينتج عنه صفر طلبات وقت تشغيل، مع توليد size-adjust تلقائياً لإزالة CLS.
س لماذا لا يتم تحميل صورة LCP الخاصة بي مسبقاً تلقائياً؟
ج تحقق مما إذا كانت سمة priority مُعينة على صورة LCP. Next.js يضيف <link rel="preload"> فقط للصور التي لديها priority. بالإضافة إلى ذلك، تكوين remotePatterns غير الصحيح يمكن أن يتسبب أيضاً في فشل التحسين.
س أيهما أختار، AVIF أم WebP؟
ج قم بتكوين كليهما ودع المتصفح يختار تلقائياً باستخدام وسم <picture>. AVIF يوفر ضغطاً أعلى لكنه أبطأ في فك الترميز، مما يجعله مناسباً للصور غير LCP؛ WebP لديه توافق أفضل (96%) ومناسب لصور LCP. Next.js يُولد كلا التنسيقين افتراضياً.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): قم بتكوين next/image في المشروع، استخدم remotePatterns لتمكين مصدر صور Unsplash، ونفذ مكون صورة متجاوب (يشمل sizes و placeholder="blur").

  2. تمرين متقدم (⭐⭐): قارن تأثير جودة الصورة المختلفة (50/75/100) والتنسيقات المختلفة (JPEG/WebP/AVIF) على LCP للصفحة، وسجل حجم الملف ووقت التحميل لكل تركيبة، وحدد التكوين الأمثل.

  3. تحد (⭐⭐⭐): قم ببناء حل تحسين CWV كامل: next/font بتكوين خطين متغيرين (خط النص الأساسي + خط الكود)، تكوين صور LCP بـ priority + عناصر نائبة blur، useReportWebVitals للإبلاغ عن المقاييس إلى API مخصص، وتحقيق درجة Lighthouse Performance ≥ 90 في النهاية.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%