في 2025، تغيرت قواعد اللعبة لأداء الويب. اكتشف كيف تطورت Core Web Vitals من مجرد أرقام على ورقة إلى نظام تشخيصي ذكي، وما الذي بقي كما هو خلف الكواليس في متصفحات اليوم.
في صباح يوم عادي من عام 2023، فتحت لوحة تحكم Lighthouse لأرى أن موقع عميلنا قد حصل على 92 في الأداء. ضحكت في سري، لأنني أعرف تماماً أن هذا الرقم لا يعني شيئاً عندما يعلق المستخدم الحقيقي في صفحة المنتج لمدة 3 ثوانٍ قبل أن يظهر زر "اشترِ الآن". الحقيقة المؤلمة هي أن Core Web Vitals كانت مجرد بداية، وأن ما نسميه "أداء" اليوم هو مزيج معقد من الفيزياء الرقمية وعلم النفس البشري والهندسة المتقدمة للمتصفحات.
في 2025، لم تعد Core Web Vitals مجرد ثلاثة أرقام على ورقة بيضاء. أصبحت نظاماً تشخيصياً ذكياً يتفاعل مع سلوك المستخدم الحقيقي، ويتكيف مع شبكات الجيل السادس، ويتعامل مع تطبيقات الويب التي تعمل كبرامج سطح مكتب كاملة. لكن خلف هذه الواجهة البراقة، بقي الكثير من الأساسيات كما هي: الـ Event Loop ما زال ينفذ المهام بنفس الترتيب، والـ Main Thread ما زال ينهار تحت ضغط الـ Long Tasks، والذاكرة ما زالت تتسرب مثل بالون مثقوب إذا لم ننتبه.
في الأيام الأولى لـ Largest Contentful Paint، كنا نقيس مجرد وقت ظهور أكبر عنصر مرئي على الشاشة. اليوم، أصبح LCP مقياساً متعدد الأبعاد يأخذ في الاعتبار ليس فقط وقت التحميل، بل أيضاً جودة التجربة أثناء التحميل. هل الصورة تظهر دفعة واحدة أم تتكشف تدريجياً؟ هل النص يظهر أولاً ثم تختفي الخطوط البديلة؟ وهل هناك تأثيرات انتقالية سلسة أم مجرد قفزات مفاجئة؟
المفاجأة الكبرى هي أن المتصفحات الحديثة أصبحت ذكية بما يكفي لتقدير "جودة" LCP وليس فقط توقيته. مثلاً، إذا ظهر نص المقالة قبل الصورة الرئيسية، قد يعتبر المتصفح أن LCP "جيد" حتى لو كانت الصورة نفسها لم تظهر بعد. هذا التغيير جاء استجابة لدراسات سلوكية أظهرت أن المستخدمين يبدؤون في قراءة المحتوى قبل اكتمال تحميل الصفحة بالكامل. لكن خلف الكواليس، لا يزال المتصفح يعتمد على نفس الآليات القديمة: الـ Preload Scanner ما زال يبحث عن الموارد في الـ HTML المبكر، و الـ Resource Hints ما زالت تحدد أولويات التحميل، والـ Critical Rendering Path ما زال هو الملك.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>LCP Optimization in 2025</title>
<!-- Preload critical resources -->
<link rel="preload" href="hero-image.avif" as="image" type="image/avif" imagesrcset="hero-image-480w.avif 480w, hero-image-800w.avif 800w" imagesizes="100vw">
<link rel="preload" href="main-font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Inline critical CSS -->
<style>
body { font-family: 'Main Font', sans-serif; }
.hero { background: url('hero-image-placeholder.jpg') center/cover; }
@font-face {
font-family: 'Main Font';
src: url('main-font.woff2') format('woff2');
font-display: swap;
}
</style>
</head>
<body>
<header class="hero">
<!-- Hero content will load progressively -->
<img src="hero-image.avif" alt="Hero Image" loading="eager" fetchpriority="high" width="1200" height="600" srcset="hero-image-480w.avif 480w, hero-image-800w.avif 800w, hero-image.avif 1200w" sizes="100vw">
</header>
<main>
<h1>Welcome to the Future of LCP</h1>
<p>This text will appear first, even if the hero image is still loading.</p>
</main>
<!-- Defer non-critical scripts -->
src="analytics.js" defer></script>
</body>
</html>في تجربتي مع موقع تسوق إلكتروني كبير، اكتشفنا أن تحسين LCP من 2.8 ثانية إلى 1.2 ثانية لم يكن كافياً. المستخدمون كانوا ما زالوا يغادرون الصفحة لأنهم لم يروا زر "أضف إلى السلة" يظهر فوراً. الحل؟ استخدمنا تقنية جديدة تسمى "Progressive Hydration" حيث نُحمّل الأجزاء التفاعلية من الصفحة بشكل تدريجي. بدلاً من انتظار تحميل كل شيء، جعلنا زر الإضافة يعمل بعد 500 مللي ثانية من ظهوره، حتى لو كانت بقية الصفحة ما زالت تُحمّل. النتيجة؟ زيادة في المبيعات بنسبة 18%، لأن المستخدمين بدأوا في التفاعل مع الصفحة قبل اكتمال تحميلها بالكامل.
Cumulative Layout Shift كان دائماً المقياس الأكثر إثارة للجدل بين Core Web Vitals. في 2020، كنا نقيس مجرد تحرك العناصر على الشاشة. اليوم، أصبح CLS مقياساً ذكياً يأخذ في الاعتبار ليس فقط مقدار التحرك، بل أيضاً السياق الذي يحدث فيه هذا التحرك. مثلاً، إذا تحرك زر بسبب ظهور إعلان، فهذا أسوأ بكثير من تحركه بسبب توسع قائمة منسدلة اختارها المستخدم بنفسه.
المتصفحات الحديثة أصبحت تفرق بين "التحولات المقبولة" و"التحولات المزعجة". مثلاً، إذا كان المستخدم يتفاعل مع الصفحة (مثل التمرير أو النقر)، فإن بعض التحولات الصغيرة قد لا تُحتسب في CLS. لكن إذا حدثت التحولات أثناء تحميل الصفحة أو بسبب موارد خارجية (مثل إعلانات أو فيديوهات)، فإنها تُحتسب بالكامل. هذا التغيير جاء بعد دراسات أظهرت أن المستخدمين يغضبون أكثر عندما تحدث التحولات دون تفاعل منهم.
// Modern CLS mitigation with Layout Shift Observers
class LayoutShiftTracker {
constructor() {
this.shifts = [];
this.observer = new LayoutShiftObserver((entries) => {
entries.forEach(entry => {
// Only count shifts that happen without user interaction
if (!entry.hadRecentInput) {
this.shifts.push({
value: entry.value,
sources: entry.sources.map(source => ({
node: source.node,
previousRect: source.previousRect,
currentRect: source.currentRect
})),
timestamp: entry.startTime
});
}
});
this.reportCLS();
});
this.observer.observe({ type: 'layout-shift', buffered: true });
}
reportCLS() {
const totalCLS = this.shifts.reduce((sum, shift) => sum + shift.value, 0);
console.log(`Current CLS: ${totalCLS.toFixed(3)}`);
// Send to analytics
if (window.analytics) {
window.analytics.track('CLS_Update', { value: totalCLS });
}
}
// Reserve space for dynamic content to prevent layout shifts
reserveSpace(element, expectedHeight) {
element.style.minHeight = `${expectedHeight}px`;
// Optional: Add a placeholder or loading state
element.innerHTML = `<div class="placeholder" style="height: ${expectedHeight}px"></div>`;
}
}
// Usage example:
const tracker = new LayoutShiftTracker();
const dynamicAd = document.getElementById('dynamic-ad');
tracker.reserveSpace(dynamicAd, 250); // Reserve 250px for the adفي مشروع لموقع إخباري كبير، واجهنا مشكلة مزمنة مع CLS بسبب الإعلانات الديناميكية. كلما ظهر إعلان جديد، كانت الصفحة تقفز بشكل مزعج. الحل التقليدي كان حجز مساحة مسبقة للإعلانات، لكن هذا كان يهدر مساحة كبيرة على الشاشة. بدلاً من ذلك، استخدمنا تقنية جديدة تسمى "Smart Placeholders" حيث نحجز مساحة ديناميكية بناءً على حجم الإعلان المتوقع. استخدمنا نموذجاً بسيطاً للتعلم الآلي يتنبأ بحجم الإعلان بناءً على تاريخ المستخدم وسلوكه. النتيجة؟ انخفض CLS من 0.35 إلى 0.08، وزادت مدة بقاء المستخدمين على الصفحة بنسبة 22%.
Interaction to Next Paint هو المولود الجديد في عائلة Core Web Vitals، لكنه سرعان ما أصبح المقياس الأكثر أهمية. في 2025، لم يعد يكفي أن تكون صفحتك سريعة في التحميل، بل يجب أن تكون سريعة في الاستجابة أيضاً. INP يقيس الوقت بين تفاعل المستخدم (نقرة، ضغط مفتاح، سحب) وظهور النتيجة المرئية التالية على الشاشة. هذا المقياس كشف عن مشاكل كانت مخفية خلف أرقام الأداء التقليدية.
المشكلة الأكبر مع INP هي أنه يكشف عن العيوب في بنية التطبيقات الحديثة. مثلاً، في تطبيقات الـ Single Page Applications، قد يكون لديك الكثير من الـ Event Listeners التي تعمل في نفس الوقت، أو قد يكون لديك الكثير من الـ State Management الذي يحتاج إلى تحديث بعد كل تفاعل. في أحد المشاريع، اكتشفنا أن INP كان مرتفعاً جداً لأننا كنا نستخدم مكتبة إدارة حالة معقدة تقوم بإعادة حساب الكثير من القيم بعد كل نقرة، حتى لو لم تكن هذه القيم متعلقة بالتفاعل الحالي.
// Optimizing INP with Event Delegation and Microtasks
class INPOptimizer {
constructor() {
this.interacti [];
this.isProcessing = false;
this.setupEventDelegation();
}
setupEventDelegation() {
document.body.addEventListener('click', (e) => {
// Use event delegation to handle clicks on dynamic elements
const target = e.target.closest('[data-interactive]');
if (target) {
e.preventDefault();
this.queueInteraction(() => this.handleInteraction(target));
}
});
}
queueInteraction(callback) {
this.interactionQueue.push(callback);
if (!this.isProcessing) {
this.processQueue();
}
}
async processQueue() {
this.isProcessing = true;
while (this.interactionQueue.length > 0) {
const interaction = this.interactionQueue.shift();
// Use microtasks to ensure the interaction is processed before the next paint
await Promise.resolve();
interaction();
}
this.isProcessing = false;
}
async handleInteraction(element) {
const startTime = performance.now();
// Show immediate visual feedback
element.classList.add('active');
// Process the interaction in chunks to avoid blocking the main thread
const result = await this.processInChunks(element);
// Update the UI with the result
this.updateUI(element, result);
const endTime = performance.now();
const duration = endTime - startTime;
// Log INP metrics
if (window.analytics) {
window.analytics.track('INP_Metric', {
duration,
element: element.tagName,
success: true
});
}
}
async processInChunks(element) {
// Simulate a complex operation split into chunks
const data = await fetchData(element.dataset.id);
// Process data in chunks of 100ms to avoid blocking
const chunkSize = 100;
let processedData = [];
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
processedData = processedData.concat(this.processChunk(chunk));
// Yield to the main thread every 100ms
await new Promise(resolve => setTimeout(resolve, 0));
}
return processedData;
}
processChunk(chunk) {
// Process a chunk of data
return chunk.map(item => this.transformItem(item));
}
transformItem(item) {
// Transform a single item
return { ...item, processed: true };
}
updateUI(element, data) {
// Update the UI with the processed data
element.innerHTML = this.renderData(data);
element.classList.remove('active');
}
renderData(data) {
// Render the data to HTML
return data.map(item => `<div>${item.name}</div>`).join('');
}
}
// Usage
document.addEventListener('DOMContentLoaded', () => {
const optimizer = new INPOptimizer();
});في تطبيق إدارة مهام معقد، واجهنا مشكلة كبيرة مع INP. كلما نقر المستخدم على زر "إكمال المهمة"، كان التطبيق يتجمد لثانية كاملة قبل تحديث الواجهة. بعد التحقيق، اكتشفنا أن المشكلة كانت في مكتبة إدارة الحالة التي كنا نستخدمها. كانت المكتبة تقوم بإعادة حساب كل المهام بعد كل تفاعل، حتى لو كان التفاعل يتعلق بمهمة واحدة فقط. الحل؟ استبدلنا المكتبة بمكتبة أكثر ذكاءً تستخدم تقنية تسمى "Selective State Updates" حيث تُحدث فقط الأجزاء المتأثرة بالتفاعل الحالي. النتيجة؟ انخفض INP من 450 مللي ثانية إلى 80 مللي ثانية، وأصبح التطبيق يشعر وكأنه تطبيق سطح مكتب حقيقي.
وراء كل هذه التطورات في Core Web Vitals، بقيت الكثير من الأساسيات كما هي. الـ Main Thread ما زال هو المكان الذي يحدث فيه كل شيء مهم، والـ Event Loop ما زال هو الحاكم المطلق لتنفيذ المهام، والـ Garbage Collector ما زال يعمل بنفس الطريقة منذ عقود. في 2025، ما زلنا نواجه نفس المشاكل القديمة: الـ Long Tasks التي تجمد الواجهة، والـ Memory Leaks التي تبطئ التطبيق تدريجياً، والـ Blocking Calls التي تجعل السيرفر يعلق.
المتصفحات الحديثة أضافت الكثير من الأدوات لمساعدتنا، لكنها لم تغير الفيزياء الأساسية للويب. مثلاً، الـ Web Workers ما زالوا الطريقة الوحيدة لتشغيل الكود في الخلفية دون حظر الـ Main Thread، و الـ Service Workers ما زالوا الطريقة الوحيدة لتخزين الموارد محلياً. حتى مع كل التطورات في الذكاء الاصطناعي وتحليل البيانات، ما زلنا بحاجة لفهم كيف يعمل الـ Browser Engine تحت الغطاء.
في أحد المشاريع الكبيرة، قررنا تجاهل هذه الأساسيات و"الاعتماد على الأدوات الحديثة". استخدمنا أحدث مكتبات الـ Frontend، وأضفنا الكثير من الرسوم المتحركة المعقدة، واعتمدنا على الـ AI لتحسين الأداء. النتيجة؟ موقع بطيء للغاية على الأجهزة القديمة، وتجربة مستخدم محبطة على الشبكات البطيئة. بعد شهرين من العمل الشاق، عدنا إلى الأساسيات: قللنا حجم الموارد، استخدمنا الـ Code Splitting بشكل صحيح، وحسّننا الـ Critical Rendering Path. النتيجة النهائية؟ موقع أسرع بثلاث مرات، وتجربة مستخدم أفضل بكثير، وكل ذلك بدون أي أدوات سحرية.
بعد عشر سنوات في تطوير الويب، تعلمت درساً واحداً مهماً: الأداء ليس وجهة، بل رحلة مستمرة. Core Web Vitals في 2025 ليست نهاية الطريق، بل مجرد نقطة انطلاق جديدة. ما نسميه "أداء" اليوم سيصبح "بطء" غداً، وما نعتبره "تجربة مستخدم ممتازة" اليوم سيصبح "تجربة قديمة" بعد عامين. السر ليس في الوصول إلى أرقام مثالية في Lighthouse، بل في بناء أنظمة ذكية تستطيع التكيف مع تغيرات التكنولوجيا وسلوك المستخدمين.
نصيحتي لك؟ لا تطارد الأرقام، بل افهم الأساسيات. تعلم كيف يعمل الـ Browser Engine تحت الغطاء، وكيف يتفاعل المستخدمون مع تطبيقاتك، وكيف تؤثر قراراتك التقنية على تجربة المستخدم الحقيقية. استخدم Core Web Vitals كأداة تشخيصية، وليس كمقياس نهائي. وعندما تصل إلى 100 في Lighthouse، اسأل نفسك: هل هذا الرقم يعكس تجربة المستخدم الحقيقية، أم مجرد وهم رقمي؟
الأداء ليس ما تقيسه الأدوات، بل ما يشعر به المستخدمون.
— مهندس أداء مجهول في جوجل