في 2025، لم يعد الأداء مجرد أرقام في لوحة تحكم. Core Web Vitals تطورت لتصبح مقياساً حقيقياً لتجربة المستخدم، لكن معظم المطورين ما زالوا يعالجونها كأرقام سطحية. هذا المقال يكشف لماذا فشلت أغلب المواقع في تحسينها، وما الذي تغير فعلاً تحت الغطاء.
في صيف 2024، أطلقنا موقعاً جديداً لشركة تجارة إلكترونية كبرى. بعد شهرين من العمل الشاق، حققنا درجات مثالية في Lighthouse: 100 في الأداء، 100 في أفضل الممارسات. لكن بعد أسبوعين من الإطلاق، انهار معدل التحويل بنسبة 30%. السبب؟ المستخدمون كانوا يغادرون الصفحة قبل أن تظهر محتوياتها بالكامل، رغم أن الـ First Contentful Paint كان يسجل 0.8 ثانية. المشكلة لم تكن في السرعة، بل في كيفية شعور المستخدم بالحركة والتفاعل خلف الكواليس. هذا هو بالضبط ما تغير في Core Web Vitals في 2025: لم تعد مجرد أرقام، بل أصبحت تجربة حية.
الغريب أن معظم المقالات التي تقرأها عن Core Web Vitals ما زالت تتحدث عن نفس النقاط القديمة: ضغط الصور، تحميل الجافاسكريبت بشكل غير متزامن، وتقليل عدد الطلبات. هذه الأمور أصبحت أساسيات منذ 2020، لكنها لم تعد كافية. في 2025، يتوقع المستخدمون تجربة سلسة حتى في أسوأ الظروف: شبكات بطيئة، أجهزة قديمة، ومتصفحات غير محدثة. وهذا يتطلب فهماً أعمق لما يحدث خلف الأرقام البراقة في أدوات القياس.
كان Largest Contentful Paint (LCP) هو نجم Core Web Vitals لسنوات. كنا نقيس الوقت الذي يستغرقه أكبر عنصر في الصفحة ليظهر، ونعتبر المهمة منتهية إذا حققنا أقل من 2.5 ثانية. لكن في 2023، أدرك جوجل أن هذا المقياس وحده مضلل. تخيل أنك تفتح صفحة ويب، ويظهر لك العنوان الرئيسي بسرعة، لكنك لا تستطيع التمرير أو النقر على أي زر لمدة 3 ثوانٍ إضافية. هذا بالضبط ما كان يحدث في معظم المواقع: LCP جيد، لكن تجربة المستخدم كانت كارثية.
لهذا السبب تم استبدال First Input Delay (FID) بـ Interaction to Next Paint (INP) في مارس 2024. الفرق الأساسي هو أن FID كان يقيس فقط الاستجابة لأول تفاعل، بينما INP يقيس كل التفاعلات خلال جلسة المستخدم. هذا التغيير لم يكن مجرد تحديث بسيط في المقياس، بل كان تحولاً جذرياً في كيفية تفكيرنا في الأداء. الآن، لا يكفي أن يكون موقعك سريعاً في البداية، بل يجب أن يبقى سريعاً طوال فترة استخدام الصفحة.
// مثال على كيفية قياس INP عملياً باستخدام PerformanceObserver
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries().filter(entry => entry.interactionId);
entries.forEach(entry => {
const { duration, interactionId, name, startTime } = entry;
console.log(`Interaction: ${name}, Duration: ${duration}ms`);
// في الإنتاج، ترسل هذه البيانات إلى أداة تحليل مثل Google Analytics
if (duration > 200) { // الحد الأقصى المقبول لـ INP
console.warn(`Slow interaction detected: ${name} took ${duration}ms`);
}
});
});
observer.observe({
type: 'event',
buffered: true,
durationThreshold: 16 // الحد الأدنى للتسجيل بالمللي ثانية
});
// المشكلة الحقيقية: حتى مع هذا الكود، قد لا ترى التفاعلات البطيئة
// لأن بعض الأحداث تُعالج في الـ Event Loop بعد انتهاء مدة الـ INP
// الحل: استخدام setTimeout مع 0 لتأخير المعالجة غير الحرجة
button.addEventListener('click', (e) => {
setTimeout(() => {
// المعالجة الثقيلة هنا
heavyProcessing();
}, 0);
});الفرق بين FID و INP يشبه الفرق بين قياس سرعة السيارة عند الانطلاق من الصفر، وقياس سرعتها طوال الرحلة. في الماضي، كنا نركز فقط على لحظة الانطلاق، والآن أصبح علينا مراقبة الأداء باستمرار. هذا يعني أن أي جزء من الكود يمكن أن يصبح عنق الزجاجة، حتى لو كان يعمل بشكل مثالي في بيئة التطوير المحلية. مثلاً، مكتبة تحليلية بسيطة يمكن أن تضيف 100 مللي ثانية إلى كل تفاعل إذا لم يتم تحميلها بشكل صحيح، وهذا يكفي لتدمير تجربة المستخدم على الأجهزة الضعيفة.
في عام 2022، كان Cumulative Layout Shift (CLS) هو المقياس الذي يتجاهله معظم المطورين. كنا نعتبره مشكلة تصميم أكثر منه مشكلة أداء. لكن في 2025، أصبح CLS أحد أهم المؤشرات على جودة تجربة المستخدم. السبب؟ المستخدمون أصبحوا أكثر حساسية للحركة غير المتوقعة في الواجهة، خاصة مع انتشار التطبيقات التي تعتمد على التمرير اللانهائي والتحميل الكسول.
المشكلة الحقيقية مع CLS ليست في العناصر التي تتحرك بشكل واضح، بل في العناصر التي تتحرك بشكل غير ملحوظ. مثلاً، إعلان يظهر بعد 3 ثوانٍ من تحميل الصفحة يمكن أن يسبب إزاحة صغيرة في المحتوى، لكن هذه الإزاحة قد تكون كافية لجعل المستخدم ينقر على زر خاطئ. في أحد المشاريع التي عملت عليها، اكتشفنا أن زر "إضافة إلى السلة" كان يتحرك بمقدار 20 بكسل بعد تحميل صورة المنتج، وهذا كان يكفي لتقليل معدل النقر عليه بنسبة 15%.
<!-- مثال على كيفية منع CLS باستخدام aspect-ratio و skeleton loaders -->
<div class="product-card">
<!-- استخدام aspect-ratio لضمان مساحة ثابتة للصورة -->
<div class="product-image-container" style="aspect-ratio: 1/1;">
<img
class="product-image"
src="product.jpg"
loading="lazy"
alt="Product"
width="300"
height="300"
style="content-visibility: auto;"
>
<!-- skeleton loader يظهر حتى تحميل الصورة -->
<div class="skeleton-loader"></div>
</div>
<h3 class="product-title">Product Name</h3>
<button class="add-to-cart">إضافة إلى السلة</button>
</div>
<style>
.product-image-container {
position: relative;
width: 100%;
background: #f0f0f0;
}
.product-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 0.3s;
}
.product-image.loaded {
opacity: 1;
}
.skeleton-loader {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
>
document.querySelectorAll('.product-image').forEach(img => {
img.addEventListener('load', () => {
img.classList.add('loaded');
img.parentElement.querySelector('.skeleton-loader').style.display = 'none';
});
// في حالة فشل تحميل الصورة
img.addEventListener('error', () => {
img.style.display = 'none';
img.parentElement.querySelector('.skeleton-loader').style.display = 'none';
});
});
</script>الحل الحقيقي لمشكلة CLS ليس فقط في تحديد أبعاد العناصر مسبقاً، بل في إعادة التفكير في كيفية تحميل المحتوى الديناميكي. على سبيل المثال، في صفحات المنتجات التي تحتوي على مراجعات المستخدمين، بدلاً من تحميل كل المراجعات دفعة واحدة، يمكنك تحميل أول 3 مراجعات فقط، ثم تحميل الباقي عند التمرير. هذا يقلل من احتمالية حدوث إزاحة كبيرة في المحتوى. أيضاً، استخدام خاصية content-visibility في CSS يمكن أن يقلل بشكل كبير من وقت تحميل الصفحة دون التأثير على تجربة المستخدم.
معظم المقالات تتحدث عن Core Web Vitals وكأنها تتعلق فقط بالشبكة والـ Rendering. لكن الحقيقة هي أن الأداء الحقيقي يتحدد في مكانين: الذاكرة والمعالج. في عام 2025، أصبح الـ Memory Leak أحد أكبر أعداء الأداء، خاصة مع انتشار تطبيقات الويب المعقدة التي تعتمد على مكتبات مثل React و Angular.
المشكلة تبدأ عندما لا ننتبه إلى كيفية إدارة الذاكرة في تطبيقاتنا. مثلاً، في تطبيق React، إذا لم تقم بإلغاء الاشتراك من الـ Event Listeners عند إلغاء تحميل المكون، ستترك مراجعاً نشطة في الذاكرة. هذه المراجع تتراكم مع الوقت، وتجعل المتصفح يستهلك المزيد من الذاكرة، مما يؤدي إلى تباطؤ تدريجي في الأداء. في أحد المشاريع التي عملت عليها، اكتشفنا أن تطبيق React كان يستهلك 1.2 جيجابايت من الذاكرة بعد 10 دقائق من الاستخدام، وهذا كان يكفي لجعل الصفحة تتجمد على الأجهزة الضعيفة.
// مثال على كيفية تجنب Memory Leaks في React
import { useEffect } from 'react';
function UserProfile({ userId }) {
useEffect(() => {
// الاشتراك في حدث
const handleResize = () => {
console.log('Window resized');
};
window.addEventListener('resize', handleResize);
// الاشتراك في WebSocket
const ws = new WebSocket(`wss://example.com/updates/${userId}`);
ws. (event) => {
console.log('New message:', event.data);
};
// دالة cleanup لإلغاء الاشتراكات
return () => {
window.removeEventListener('resize', handleResize);
ws.close(); // إغلاق الاتصال لمنع تسرب الذاكرة
};
}, [userId]); // إعادة التشغيل عند تغيير userId
return <div>User Profile: {userId}</div>;
}
// مشكلة شائعة أخرى: عدم إلغاء setInterval
useEffect(() => {
const interval = setInterval(() => {
console.log('This runs every second');
}, 1000);
return () => clearInterval(interval); // عدم إلغاء هذا سيؤدي إلى تسرب الذاكرة
}, []);
// في التطبيقات الكبيرة، استخدم أدوات مثل Chrome DevTools Memory Tab
// لقياس استهلاك الذاكرة وتحديد التسريباتلكن الذاكرة ليست المشكلة الوحيدة. المعالج أيضاً يلعب دوراً حاسماً في الأداء. في الماضي، كنا نركز فقط على تقليل وقت تنفيذ الجافاسكريبت، لكن في 2025، أصبح علينا التفكير في كيفية توزيع الحمل على الـ Event Loop. مثلاً، إذا كان لديك دالة تستغرق 500 مللي ثانية للتنفيذ، فهي ستجمد واجهة المستخدم بالكامل، حتى لو كانت تعمل في الخلفية. الحل هو تقسيم هذه الدالة إلى أجزاء صغيرة باستخدام setTimeout أو requestIdleCallback، مما يسمح للمتصفح بمعالجة الأحداث الأخرى بين الأجزاء.
في عام 2020، كانت Lighthouse هي الأداة الوحيدة التي نحتاجها لقياس الأداء. كنا نفتح تبويب Audits في Chrome DevTools، ونشغل الاختبار، ونعتبر المهمة منتهية. لكن في 2025، أصبحت Lighthouse مجرد نقطة بداية. المشكلة هي أن Lighthouse تقيس الأداء في بيئة مثالية: جهاز قوي، شبكة سريعة، ولا يوجد مستخدمون آخرون على الصفحة. هذا لا يعكس الواقع أبداً.
الآن، نحتاج إلى أدوات أكثر تقدماً لقياس الأداء في ظروف حقيقية. مثلاً، WebPageTest يسمح لنا باختبار الأداء على أجهزة مختلفة وشبكات متنوعة. كذلك، أصبح من الضروري استخدام Real User Monitoring (RUM) لجمع بيانات الأداء من المستخدمين الفعليين. في أحد المشاريع، اكتشفنا أن متوسط LCP في Lighthouse كان 1.8 ثانية، لكن في بيانات RUM الحقيقية، كان المتوسط 4.2 ثانية على الأجهزة المحمولة في دول نامية.
لكن حتى مع هذه الأدوات، تبقى المشكلة الأكبر هي كيفية تفسير البيانات. مثلاً، إذا كان INP مرتفعاً، هل المشكلة في الجافاسكريبت، في الشبكة، أم في قاعدة البيانات؟ هنا تأتي أهمية فهم ما يحدث خلف الكواليس. في أحد المشاريع، اكتشفنا أن INP كان مرتفعاً بسبب استعلام معقد لقاعدة البيانات كان يستغرق 800 مللي ثانية. لم نكن لنكتشف هذا لو اعتمدنا فقط على أدوات قياس الأداء في المتصفح.
على الرغم من كل التغييرات في Core Web Vitals، هناك بعض الأساسيات التي بقيت كما هي منذ بداية الويب. أولاً، حجم الصفحة لا يزال مهماً. في عام 2025، أصبح متوسط حجم الصفحة 3.2 ميجابايت، وهذا رقم كارثي. كل كيلوبايت إضافي يعني المزيد من الوقت للتحميل، خاصة على الشبكات البطيئة. ثانياً، عدد الطلبات لا يزال مهماً. حتى مع HTTP/2 و HTTP/3، كل طلب إضافي يعني المزيد من الوقت المستغرق في التفاوض على الاتصال.
ثالثاً، ترتيب تحميل الموارد لا يزال حاسماً. في الماضي، كنا نركز فقط على تحميل الجافاسكريبت بشكل غير متزامن. الآن، أصبح علينا التفكير في أولوية تحميل كل مورد. مثلاً، يجب تحميل الخطوط والصور الأساسية في بداية الصفحة، بينما يمكن تأجيل تحميل الصور غير المرئية باستخدام loading="lazy". كذلك، استخدام preload و prefetch يمكن أن يقلل بشكل كبير من وقت تحميل الموارد الحرجة.
<!-- مثال على استخدام preload و prefetch بشكل فعال -->
<head>
<!-- تحميل الخط الأساسي مسبقاً -->
<link rel="preload" href="fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
<!-- تحميل الصورة الرئيسية مسبقاً -->
<link rel="preload" href="hero-image.jpg" as="image">
<!-- تحميل ملف الجافاسكريبت الرئيسي -->
<link rel="preload" href="main.js" as="script">
<!-- تحميل الصفحة التالية مسبقاً (للتطبيقات ذات الصفحة الواحدة) -->
<link rel="prefetch" href="next-page.html" as="document">
<!-- تحميل مكتبة تحليلية مسبقاً (غير حرجة) -->
<link rel="prefetch" href="analytics.js" as="script">
</head>
<!-- تحميل الصور بشكل كسول -->
<img src="image.jpg" loading="lazy" alt="Description" width="800" height="600">
<!-- تحميل فيديو بشكل كسول -->
<video controls width="800" height="450" preload="metadata">
<source src="video.mp4" type="video/mp4">
</video>رابعاً، التخزين المؤقت لا يزال مهماً. في عام 2025، أصبح بإمكاننا تخزين المزيد من البيانات على جهاز المستخدم باستخدام تقنيات مثل Service Workers و Cache API. لكن المشكلة هي أن الكثير من المطورين ما زالوا لا يستخدمون هذه التقنيات بشكل صحيح. مثلاً، في أحد المشاريع، اكتشفنا أن التطبيق كان يقوم بتحميل نفس ملفات الجافاسكريبت في كل زيارة، رغم أنها لم تتغير منذ أشهر. ببساطة، إضافة رؤوس Cache-Control المناسبة كان يمكن أن يقلل وقت التحميل بنسبة 40%.
إذا كان هناك شيء واحد يجب أن تأخذه من هذا المقال، فهو هذا: توقف عن التركيز على الأرقام في Lighthouse، وابدأ في قياس ما يشعر به المستخدمون حقاً. قم بتشغيل أداة Real User Monitoring، واجلس مع مستخدمين حقيقيين، وشاهد كيف يتفاعلون مع موقعك. ستكتشف أن معظم مشاكل الأداء لا تظهر في أدوات القياس التقليدية، بل في التفاصيل الصغيرة التي لا ينتبه إليها أحد.
في عام 2025، الأداء ليس مجرد ميزة، بل هو أساس تجربة المستخدم. إذا كان موقعك بطيئاً، فلن يهتم المستخدمون إذا كان لديك أفضل تصميم أو أفضل محتوى. ابدأ اليوم بقياس INP و CLS في ظروف حقيقية، وركز على ما يحدث خلف الكواليس في الذاكرة والمعالج. هذه هي الطريقة الوحيدة لبناء مواقع ويب سريعة حقاً، وليست مجرد مواقع تبدو سريعة في الاختبارات.