في ٢٠٢٥ لم تعد Core Web Vitals مجرد أرقام في تقرير Lighthouse، بل أصبحت بوصلة حقيقية لتجربة المستخدم. اكتشف كيف تغيرت المقاييس، ما الذي أصبح يبطئ المواقع فعلاً، وكيف تعالجها قبل أن يخسر موقعك نصف زواره.
في صيف ٢٠٢٤، أطلقنا تحديثاً لموقع عميل في الشرق الأوسط كان يحقق ٩٢ على Lighthouse. بعد أسبوعين، انخفضت زيارات الصفحة الرئيسية بنسبة ٤٠٪ دون أي تغيير في المحتوى أو الحملات التسويقية. السبب؟ لم نعد نحقق عتبة Core Web Vitals الجديدة التي أطلقها جوجل في مارس ٢٠٢٥. لم يكن الخطأ في الـ TTFB أو حجم الصور، بل في شيء أكثر دهاءً: الـ Interaction to Next Paint (INP) الذي أصبح الآن رسمياً جزءاً من الثلاثي الذهبي بدلاً من الـ First Input Delay (FID).
الواقع المؤلم هو أن معظم المطورين ما زالوا يعالجون الأداء كمسألة جانبية، يلقون ببعض التحسينات السطحية قبل الإطلاق ثم ينسونها. لكن في ٢٠٢٥، أصبحت Core Web Vitals جزءاً لا يتجزأ من تجربة المستخدم الحقيقية، وليس مجرد مقياس تقني. الشركات التي تتجاهلها تخسر ليس فقط ترتيبها في البحث، بل ثقة المستخدمين الذين أصبحوا أكثر حساسية للتأخير من أي وقت مضى. دعنا نفتح غطاء المحرك ونرى ما الذي تغير حقاً تحت الغطاء.
منذ عام ٢٠٢٠، كنا نركز على ثلاث مقاييس رئيسية: Largest Contentful Paint (LCP) لقياس سرعة التحميل، First Input Delay (FID) لقياس الاستجابة الأولية، و Cumulative Layout Shift (CLS) لقياس استقرار الصفحة. لكن في مارس ٢٠٢٥، أعلن جوجل عن استبدال FID بـ Interaction to Next Paint (INP)، وهو مقياس أكثر شمولية يقيس الاستجابة الفعلية لكل تفاعل خلال جلسة المستخدم كاملة، وليس فقط التفاعل الأول.
الفرق الأساسي بين FID و INP يشبه الفرق بين قياس سرعة أول خطوة في سباق الماراثون وقياس أداء العداء طوال السباق. FID كان يقيس فقط التأخير الأولي عند النقر على زر أو إدخال نص، بينما INP يقيس كل تفاعل خلال جلسة المستخدم، بما في ذلك التفاعلات المتأخرة التي تحدث بعد تحميل الصفحة بالكامل. هذا التغيير لم يأتِ من فراغ؛ فمع تزايد تعقيد التطبيقات الحديثة التي تعتمد على الـ Single Page Applications (SPAs) و الـ JavaScript الثقيل، أصبح من الضروري قياس الأداء طوال فترة استخدام الصفحة، وليس فقط لحظة التحميل الأولى.
// مثال على كيفية قياس INP يدوياً باستخدام PerformanceObserver
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries().filter(entry => entry.interactionId);
entries.forEach(entry => {
const { startTime, processingStart, processingEnd, duration } = entry;
const inputDelay = processingStart - startTime;
const processingTime = processingEnd - processingStart;
const presentati duration - processingEnd;
console.log(`Interaction ID: ${entry.interactionId}`);
console.log(`Input Delay: ${inputDelay}ms`);
console.log(`Processing Time: ${processingTime}ms`);
console.log(`Presentation Delay: ${presentationDelay}ms`);
console.log(`Total INP: ${duration}ms`);
});
});
observer.observe({ type: 'event', buffered: true });ما الذي يجعل INP مختلفاً؟ إنه يقيس ثلاثة مكونات رئيسية لكل تفاعل: الـ Input Delay (الوقت بين حدث المستخدم وبدء المعالجة)، الـ Processing Time (الوقت الذي يستغرقه الـ Event Handler)، و الـ Presentation Delay (الوقت بين نهاية المعالجة وعرض النتيجة على الشاشة). هذا التقسيم الدقيق يسمح للمطورين بتحديد بالضبط أين يحدث التأخير، سواء كان في الـ Event Loop بسبب مهام طويلة، أو في معالجة البيانات، أو في تحديث DOM.
لسنوات، كنا نركز على تحسين LCP من خلال ضغط الصور واستخدام تنسيقات حديثة مثل WebP و AVIF. لكن في ٢٠٢٥، أصبحت المشكلة الحقيقية ليست في حجم الموارد، بل في كيفية تحميلها وتقديمها. جوجل الآن تنظر إلى ما يسمى بـ "Resource Load Priority" و "Critical Rendering Path" بشكل أكثر صرامة. على سبيل المثال، إذا كان لديك صورة كبيرة في الجزء العلوي من الصفحة، لكنك تستخدم `loading="lazy"` عليها، فستؤثر سلباً على LCP حتى لو كانت الصورة مضغوطة بشكل مثالي.
المشكلة الأكبر التي نراها في المشاريع الحديثة هي الاعتماد المفرط على الـ JavaScript لتحميل المحتوى الرئيسي. في أحد المشاريع التي عملنا عليها، كان LCP يصل إلى ٤.٢ ثانية على الرغم من أن الصورة الرئيسية كانت مضغوطة وحجمها أقل من ١٠٠ كيلوبايت. بعد التحليل، اكتشفنا أن الـ JavaScript الذي كان مسؤولاً عن تحميل الصورة كان ينتظر تحميل مكتبة خارجية قبل أن يبدأ طلب الصورة. الحل؟ استخدام `rel="preload"` للصورة الرئيسية ووضعها مباشرة في HTML بدلاً من تحميلها ديناميكياً عبر JavaScript.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Optimized LCP Example</title>
<!-- Preload the main image -->
<link rel="preload" href="hero-image.avif" as="image" type="image/avif">
<!-- Preload critical CSS -->
<link rel="preload" href="critical.css" as="style" "this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="critical.css"></noscript>
</head>
<body>
<!-- Hero image placed directly in HTML -->
<img src="hero-image.avif" alt="Hero Image" width="1200" height="600" loading="eager" fetchpriority="high">
<!-- Non-critical JavaScript loaded asynchronously -->
src="app.js" defer></script>
</body>
</html>في ٢٠٢٠، كان Cumulative Layout Shift (CLS) يقيس بشكل أساسي التحولات الناتجة عن تحميل الصور بدون أبعاد أو الإعلانات التي تُحمل ديناميكياً. لكن في ٢٠٢٥، أصبح CLS مشكلة أكبر بكثير بسبب انتشار التطبيقات الديناميكية التي تعتمد على الـ Web Components و الـ Shadow DOM و المكتبات الحديثة مثل React و Vue. المشكلة الآن ليست فقط في المحتوى الذي يُحمل بعد الصفحة، بل في المحتوى الذي يتغير باستمرار بناءً على تفاعلات المستخدم.
أحد أسوأ الأمثلة التي رأيناها كان في منصة تعليمية تستخدم React مع مكتبة الرسوم البيانية D3.js. كانت الرسوم البيانية تُعاد رسمها بالكامل عند كل تفاعل، مما يسبب تحولات كبيرة في الصفحة. الحل لم يكن بسيطاً؛ اضطررنا إلى إعادة هيكلة الكود بالكامل لاستخدام `transform` بدلاً من تغيير الأبعاد الفعلية للعناصر، واستخدام `will-change: transform` لتحسين الأداء. كما أضفنا شنات انتقالية ناعمة لمنع التحولات المفاجئة.
/* حل لتحسين CLS عند استخدام الرسوم البيانية الديناميكية */
.chart-container {
contain: layout;
will-change: transform; /* يخبر المتصفح بأن هذا العنصر سيتغير */
}
.chart-element {
transition: transform 0.3s ease-out;
/* بدلاً من تغيير width/height مباشرة، نستخدم transform */
transform: scaleX(1);
}
/* عند تحديث الرسم البياني، نستخدم transform بدلاً من تغيير الأبعاد */
.chart-element.updated {
transform: scaleX(1.2); /* تأثير سلس بدون CLS */
}أحد أكبر التحديات في تحسين INP هو التعامل مع المهام الطويلة (Long Tasks) التي تسد الـ Event Loop. في أحد المشاريع التي عملنا عليها، كان لدينا تطبيق يستخدم مكتبة معالجة البيانات الثقيلة التي تستغرق ٣٠٠ مللي ثانية لمعالجة كل طلب. المشكلة أن هذه المكتبة كانت تُشغل داخل حدث `click`، مما يسبب تأخيراً ملحوظاً في الاستجابة. الحل؟ تقسيم المهمة إلى أجزاء صغيرة باستخدام `setTimeout` أو `requestIdleCallback` لتجنب سد الـ Event Loop.
المشكلة الأكبر التي نراها في التطبيقات الحديثة هي الاعتماد المفرط على الـ JavaScript لتنفيذ المهام التي يمكن أن تُنجز على السيرفر. على سبيل المثال، بدلاً من معالجة البيانات في المتصفح، يمكن إرسال البيانات إلى سيرفر Worker للمعالجة ثم استلام النتيجة. هذا النهج لا يحسن INP فقط، بل يقلل أيضاً من استهلاك البطارية على الأجهزة المحمولة.
// تقسيم مهمة طويلة إلى أجزاء صغيرة لتحسين INP
function processLargeDataset(data, callback) {
const CHUNK_SIZE = 1000;
let index = 0;
function processChunk() {
const end = Math.min(index + CHUNK_SIZE, data.length);
for (; index < end; index++) {
// معالجة البيانات...
}
if (index < data.length) {
// استخدام setTimeout لتجنب سد الـ Event Loop
setTimeout(processChunk, 0);
} else {
callback();
}
}
processChunk();
}
// استخدام المثال
const data = Array(100000).fill(0).map((_, i) => i);
processLargeDataset(data, () => {
console.log('تمت معالجة البيانات بنجاح!');
});على الرغم من كل التغييرات في Core Web Vitals، هناك بعض التحديات التي بقيت دون تغيير منذ سنوات. الأول هو مشكلة الـ Third-Party Scripts التي لا تزال تسبب تأخيرات كبيرة في تحميل الصفحة. في أحد المشاريع، اكتشفنا أن إضافة زر "مشاركة على فيسبوك" كان يزيد LCP بمقدار ١.٥ ثانية بسبب تحميل مكتبة فيسبوك الخارجية. الحل؟ تحميل هذه السكربتات بشكل غير متزامن بعد تحميل الصفحة الرئيسية، أو استبدالها بحلول أخف مثل Web Share API.
التحدي الثاني هو مشكلة الـ Caching التي لا تزال تسبب صداعاً للمطورين. على الرغم من تطور تقنيات التخزين المؤقت مثل Service Workers و HTTP/3، ما زلنا نرى مواقع تعيد تحميل الموارد نفسها مراراً وتكراراً بسبب إعدادات خاطئة في رؤوس HTTP. القاعدة الذهبية التي نتبعها دائماً هي: "إذا لم يتغير المورد، فلا تطلبه مرة أخرى". استخدام `Cache-Control: immutable` للموارد الثابتة يمكن أن يحسن الأداء بشكل كبير.
بعد سنوات من العمل على تحسين الأداء، هذه هي النصائح العملية التي نطبقها في كل مشروع: أولاً، إذا كنت تستخدم React أو Vue، فتأكد من استخدام React Server Components أو Vue Server-Side Rendering لتقليل كمية JavaScript التي تُرسل إلى المتصفح. ثانياً، استخدم `rel="preload"` للموارد الحرجة و `fetchpriority="high"` للصور الرئيسية. ثالثاً، قسم المهام الطويلة إلى أجزاء صغيرة باستخدام `setTimeout` أو `requestIdleCallback` لتحسين INP. رابعاً، استخدم `will-change` و `contain` في CSS لتحسين CLS. وأخيراً، لا تعتمد فقط على Lighthouse؛ استخدم أدوات مثل WebPageTest و Chrome DevTools لقياس الأداء في ظروف واقعية.
الشيء الوحيد الذي لم يتغير في ٢٠٢٥ هو أن الأداء ليس مجرد ميزة تقنية، بل هو جزء أساسي من تجربة المستخدم. المواقع التي تتجاهل Core Web Vitals تخسر ليس فقط ترتيبها في البحث، بل ثقة المستخدمين الذين أصبحوا أكثر حساسية للتأخير من أي وقت مضى. ابدأ بقياس أداء موقعك اليوم، وحدد أولويات التحسينات بناءً على البيانات الحقيقية، ولا تتوقف عن المراقبة بعد الإطلاق. الأداء هو رحلة مستمرة، وليس وجهة نهائية.
الأداء ليس مجرد رقم في تقرير Lighthouse، بل هو الشعور الذي يعيشه المستخدم عندما ينقر على زر ويشعر أن التطبيق يستجيب فوراً. في ٢٠٢٥، أصبح هذا الشعور هو الفرق بين موقع ناجح وآخر يفشل.
— مهندس أداء في جوجل