في 2025، تغيرت قواعد اللعبة لأداء الويب. اكتشف كيف تطورت Core Web Vitals لتصبح لغة مشتركة بين المطورين ومحركات البحث، وما الذي بقي كما هو خلف الكواليس في متصفحات اليوم.
في صباح يوم عادي من عام 2025، فتحت لوحة تحكم مشروع ضخم لأجد أن Largest Contentful Paint (LCP) انخفض فجأة من 1.2 ثانية إلى 0.8 ثانية دون أي تغيير في الكود. ظننت أن هناك خطأ في الأداة، لكن بعد ساعات من التحقيق اكتشفت أن جوجل عدلت خوارزمية حساب LCP لتأخذ في الاعتبار وقت تحميل الخطوط الديناميكية قبل ظهور أول بكسل على الشاشة. هذه ليست مجرد تحديث بسيط في أداة، بل تحول جوهري في كيفية فهم المتصفحات للأداء. اليوم، لم تعد Core Web Vitals مجرد أرقام نتابعها في Lighthouse، بل أصبحت لغة مشتركة بين المطورين ومحركات البحث، ومعايير حاسمة تحدد ما إذا كان موقعك سيظهر في الصفحة الأولى أم سيغرق في أعماق نتائج البحث.
لكن خلف هذه الأرقام البراقة، تبقى الحقائق التقنية ثابتة: المتصفح ما زال يعمل بنفس الـ Event Loop القديم، الـ Rendering Pipeline لم يتغير كثيراً، والذاكرة ما زالت تتسرب إذا لم ننظف الـ Event Listeners. الفرق الوحيد هو أن الأدوات أصبحت أكثر ذكاءً في كشف هذه المشاكل قبل أن تؤثر على المستخدم الحقيقي. في هذا المقال، سنفكك ما تغير في Core Web Vitals منذ 2023، وما الذي بقي كما هو خلف الكواليس، وكيف يمكنك اليوم بناء واجهات سريعة لا تعتمد فقط على تحسين الصور، بل على فهم عميق لكيفية عمل المتصفح تحت الغطاء.
في الماضي، كان Largest Contentful Paint يقيس اللحظة التي يظهر فيها أكبر عنصر مرئي على الشاشة، سواء كان صورة أو نصاً أو فيديو. لكن في 2025، تغيرت اللعبة. الآن، المتصفح يأخذ في الاعتبار وقت تحميل الموارد الحرجة قبل أن يبدأ الرسم، بما في ذلك الخطوط الديناميكية والـ CSS الذي يؤثر على الـ Layout. مثلاً، إذا كان لديك خط مخصص من Google Fonts، فإن المتصفح لن يبدأ حساب LCP حتى يكتمل تحميل هذا الخط، حتى لو كان النص نفسه مرئياً بالفعل باستخدام خط احتياطي. هذا التغيير جعل LCP أكثر دقة، لكنه أيضاً كشف مشاكل لم نكن نراها من قبل.
من تجربتي في تحسين أداء موقع تجاري كبير، وجدنا أن 30% من حالات LCP السيئة كانت بسبب خطوط مخصصة يتم تحميلها بشكل غير متزامن، مما يتسبب في إعادة رسم الصفحة بعد تحميل الخط. الحل؟ استخدام font-display: swap مع خطوط احتياطية متشابهة في الحجم، وتقليل حجم ملفات الخطوط باستخدام تنسيقات مثل WOFF2. لكن الأهم هو فهم أن LCP الآن يقيس ليس فقط متى يظهر المحتوى، بل متى يصبح جاهزاً للتفاعل. هذا يعني أن تحسين LCP لم يعد يتعلق فقط بتحميل الصور بشكل أسرع، بل بتقليل الوقت بين ظهور المحتوى واستعداده للاستخدام.
<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: 'CustomFont';
src: url('custom-font.woff2') format('woff2');
font-display: swap;
}
body {
font-family: 'CustomFont', Arial, sans-serif;
/* استخدم خط احتياطي مشابه في الحجم لمنع Layout Shift */
}
</style>
<!-- Preload الخط لتسريع تحميله -->
<link rel="preload" href="custom-font.woff2" as="font" type="font/woff2" crossorigin>
</head>
<body>
<h1>عنوان الصفحة</h1>
<img src="hero-image.jpg" alt="صورة رئيسية" loading="eager" fetchpriority="high">
</body>
</html>في عام 2023، كان Cumulative Layout Shift (CLS) يقيس مقدار تحرك العناصر على الصفحة بعد تحميلها. لكن في 2025، تغير تعريف CLS ليشمل أيضاً التحولات التي تحدث أثناء التفاعل مع الصفحة، مثل النقر على زر يؤدي إلى تحميل محتوى جديد. هذا التغيير جاء بعد أن اكتشفت جوجل أن 40% من حالات CLS السيئة تحدث بعد أن يبدأ المستخدم في التفاعل مع الصفحة، وليس فقط أثناء التحميل الأولي. الآن، المتصفح يقيس CLS طوال فترة جلسة المستخدم، وليس فقط في الثواني الأولى.
من أكثر المشاكل التي واجهتها في هذا السياق كانت مع الإعلانات الديناميكية. في أحد المشاريع، كان لدينا إعلانات تُحمّل بعد 3 ثوانٍ من فتح الصفحة، مما يتسبب في تحرك المحتوى الرئيسي للأسفل بمقدار 200 بكسل. في الماضي، كان هذا لا يؤثر على CLS لأن الأدوات كانت تقيس فقط الثواني الخمس الأولى. لكن اليوم، هذا التحول يُحسب في النتيجة النهائية. الحل؟ تخصيص مساحة ثابتة للإعلانات باستخدام CSS، حتى لو كانت فارغة في البداية، لمنع التحولات المفاجئة. الكود التالي يوضح كيفية القيام بذلك:
.ad-container {
min-height: 250px; /* ارتفاع ثابت لمنع Layout Shift */
width: 100%;
background: #f5f5f5;
border: 1px dashed #ccc;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
/* عند تحميل الإعلان، سيحل محله دون تحريك المحتوى */
.ad-container.loaded {
min-height: 0;
border: none;
}في 2024، أعلنت جوجل عن استبدال First Input Delay (FID) بـ Interaction to Next Paint (INP)، وهو مقياس جديد يقيس وقت الاستجابة للتفاعلات طوال فترة جلسة المستخدم. السبب؟ FID كان يقيس فقط التأخير الأولي للتفاعل الأول، بينما INP يقيس كل تفاعل على الصفحة، من النقر على زر إلى التمرير والكتابة في حقل إدخال. هذا التغيير جاء بعد دراسات أظهرت أن 60% من المستخدمين يغادرون الصفحة إذا استغرق التفاعل أكثر من 200 مللي ثانية، حتى لو كان التحميل الأولي سريعاً.
في أحد المشاريع التي عملت عليها، كان لدينا تطبيق ويب معقد يعتمد على React، وكان INP دائماً في المنطقة الحمراء (أكثر من 500 مللي ثانية). بعد التحقيق، اكتشفنا أن المشكلة ليست في الكود نفسه، بل في كيفية تعامل المتصفح مع الـ Event Loop. كان لدينا عدد كبير من الـ Event Listeners التي تعمل في الخلفية، مما يتسبب في تأخير معالجة التفاعلات الجديدة. الحل؟ استخدام تقنيات مثل Debouncing و Throttling لتقليل عدد الأحداث التي يتم معالجتها، وتأجيل المهام غير الحرجة باستخدام requestIdleCallback. الكود التالي يوضح كيفية تحسين INP باستخدام Debouncing:
function debounce(func, timeout = 100) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
// استخدام Debounce لتأخير معالجة حدث التمرير
window.addEventListener('scroll', debounce(() => {
// معالجة التمرير هنا، سيتم تنفيذها بعد توقف المستخدم عن التمرير
updateScrollPosition();
}, 150));
// استخدام requestIdleCallback لتأجيل المهام غير الحرجة
requestIdleCallback(() => {
// تنفيذ مهام غير حرجة عندما يكون المتصفح خاملاً
preloadImages();
}, { timeout: 1000 });خلف كل مقياس من Core Web Vitals، يوجد الـ Event Loop الذي يعمل بنفس الطريقة منذ سنوات. عندما ينقر المستخدم على زر، يتم وضع هذا الحدث في الـ Event Queue، ثم يقوم الـ Event Loop بمعالجته عندما يكون الـ Call Stack فارغاً. المشكلة تحدث عندما يكون الـ Call Stack مشغولاً بمهمة طويلة، مثل معالجة كمية كبيرة من البيانات أو تنفيذ حلقة تكرارية معقدة. في هذه الحالة، يتم تأخير معالجة الحدث حتى تنتهي المهمة الحالية، مما يؤدي إلى تأخير في الاستجابة.
في أحد المشاريع، كان لدينا مشكلة في INP بسبب حلقة تكرارية طويلة في JavaScript تعالج بيانات جدول يحتوي على آلاف الصفوف. الحل؟ تقسيم المهمة إلى أجزاء أصغر باستخدام setTimeout أو requestIdleCallback، مما يسمح للـ Event Loop بمعالجة الأحداث الأخرى بين كل جزء. الكود التالي يوضح كيفية تقسيم مهمة طويلة إلى أجزاء:
function processLargeArray(array, chunkSize, callback) {
let index = 0;
function processChunk() {
const end = Math.min(index + chunkSize, array.length);
for (; index < end; index++) {
// معالجة العنصر الحالي
callback(array[index]);
}
if (index < array.length) {
// السماح للـ Event Loop بمعالجة الأحداث الأخرى
setTimeout(processChunk, 0);
}
}
processChunk();
}
// استخدام الدالة لمعالجة مصفوفة كبيرة
const data = Array.from({ length: 10000 }, (_, i) => i);
processLargeArray(data, 100, (item) => {
// معالجة كل عنصر
console.log(item);
});على الرغم من كل التطورات في أدوات قياس الأداء، تبقى الحقائق الأساسية ثابتة: المتصفح لا يزال يعتمد على ذاكرة واحدة ومعالج واحد، والـ Memory Leaks لا تزال تسبب مشاكل كبيرة في الأداء. في عام 2025، أصبحت الأدوات مثل Chrome DevTools أكثر ذكاءً في كشف الـ Memory Leaks، لكنها لا تزال تتطلب من المطورين فهم كيفية عمل الذاكرة في JavaScript.
من أكثر المشاكل التي أراها في المشاريع هي الـ Event Listeners التي لا يتم إزالتها بعد استخدام العنصر، مما يتسبب في تسرب الذاكرة. مثلاً، في تطبيق React، إذا أضفت Event Listener داخل useEffect دون إزالته في cleanup function، فإن كل مرة يتم فيها إعادة تحميل المكون، يتم إضافة Event Listener جديد دون إزالة القديم، مما يؤدي إلى تراكم الذاكرة. الكود التالي يوضح كيفية تجنب هذه المشكلة:
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
function handleScroll() {
console.log('Scroll event');
}
// إضافة Event Listener
window.addEventListener('scroll', handleScroll);
// إزالة Event Listener عند إلغاء تحميل المكون
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []); // مصفوفة التبعيات الفارغة تعني أن هذا الكود يعمل مرة واحدة فقط
return <div>My Component</div>;
}خلف كل تحديث لشاشة المتصفح، يوجد الـ Rendering Pipeline الذي يعمل بنفس الطريقة منذ سنوات: الـ Style Calculation، الـ Layout، الـ Paint، وأخيراً الـ Composite. المشكلة تحدث عندما نقوم بتغييرات تؤثر على مراحل متعددة من هذا الـ Pipeline، مثل تغيير حجم عنصر يؤثر على الـ Layout و الـ Paint و الـ Composite. في عام 2025، أصبحت الأدوات مثل Chrome DevTools تعرض هذه المراحل بشكل أكثر تفصيلاً، لكنها لا تزال تتطلب من المطورين فهم كيفية تقليل تأثير التغييرات على هذا الـ Pipeline.
في أحد المشاريع، كان لدينا مشكلة في الأداء بسبب تغييرات متكررة في حجم العناصر باستخدام JavaScript. الحل؟ استخدام تقنيات مثل CSS Containment لعزل العناصر التي تتغير بشكل متكرر، وتقليل عدد التغييرات التي تؤثر على الـ Layout. الكود التالي يوضح كيفية استخدام CSS Containment:
.dynamic-element {
contain: strict; /* عزل العنصر لمنع تأثيره على بقية الصفحة */
width: 100px;
height: 100px;
background: blue;
}
/* تغيير حجم العنصر باستخدام Transform بدلاً من Width/Height
لتجنب تأثيره على الـ Layout */
.dynamic-element.changed {
transform: scale(1.5);
}مع تزايد استخدام الـ Edge Computing وخدمات مثل Cloudflare Workers و Vercel Edge Functions، بدأ البعض يتساءل عما إذا كانت Core Web Vitals لا تزال ذات صلة. الحقيقة هي أن هذه التقنيات تساعد في تقليل وقت الاستجابة من جانب الخادم، لكنها لا تحل المشاكل التي تحدث على جهاز المستخدم، مثل الـ Rendering و الـ JavaScript Execution. في عام 2025، أصبحت Core Web Vitals أكثر أهمية من أي وقت مضى لأنها تقيس تجربة المستخدم الحقيقية على جهازه، وليس فقط سرعة الاستجابة من الخادم.
في أحد المشاريع التي استخدمت فيها Cloudflare Workers لتقليل وقت الاستجابة، وجدنا أن LCP تحسن بشكل كبير، لكن INP بقي كما هو لأن المشكلة كانت في معالجة JavaScript على جهاز المستخدم. هذا يوضح أن Core Web Vitals لا تزال ضرورية لأنها تقيس جوانب مختلفة من تجربة المستخدم، بعضها يتعلق بالخادم وبعضها يتعلق بالجهاز المحلي.
بعد سنوات من العمل على تحسين أداء الويب، إليك نصيحتي الذهبية: لا تعتمد فقط على أدوات القياس، بل افهم كيف يعمل المتصفح تحت الغطاء. استخدم Chrome DevTools لتحليل الـ Event Loop و الـ Memory Usage و الـ Rendering Pipeline، ولا تكتفِ بالنظر إلى الأرقام النهائية في Lighthouse. قم بتقسيم المهام الطويلة باستخدام setTimeout أو requestIdleCallback، واستخدم CSS Containment لعزل العناصر الديناميكية، وتأكد من إزالة الـ Event Listeners بعد استخدامها لمنع تسرب الذاكرة. وأخيراً، تذكر أن Core Web Vitals ليست مجرد أرقام، بل هي لغة مشتركة بينك وبين المتصفح، تفهمها جيداً لتتمكن من بناء واجهات سريعة وسلسة في 2025 وما بعده.