المستوى المُستهدف: متوسط (تحتاج خلفية بأساسيات JavaScript و Chrome DevTools).
لو موقعك بياخد Lighthouse score 95 على Performance، والـ LCP و CLS كلهم خضرا، لكن الزائر لسه بيشتكي إن الواجهة بتيجمد ثانية لما يضغط زر "أضف للسلة"، المشكلة مش في Lighthouse. المشكلة في مقياس اسمه INP (Interaction to Next Paint)، وده اللي بيحدد إحساس الزائر بسرعة موقعك بعد ما الصفحة تخلّص تحميل.
المشكلة بالظبط: ليه LCP الجميل بيخدعك
LCP بيقيس متى أكبر عنصر بيظهر على الشاشة. CLS بيقيس قفزات التخطيط. الاتنين بيقيسوا التحميل الأولي بس. مفيش فيهم اللي بيقيس استجابة الواجهة بعد ما الزائر يبدأ يضغط ويتفاعل.
INP بدأ Google يدخّله Core Web Vitals رسميًا في مارس 2024 بدل FID القديم. القصة بسيطة: FID كان بيقيس زمن أول تفاعل فقط، فكان من السهل تخدعه. INP بياخد كل التفاعلات (الكليكات، الكتابة، الـ taps) خلال الجلسة ويرجّعلك تقريبًا أعلى زمن استجابة من بينهم.
الحدود الرسمية: ≤ 200ms أخضر، 200–500ms أصفر، أكتر من 500ms أحمر. لو موقعك p75 INP عند 480ms، يعني ربع زوارك بيحسوا تأخير نصف ثانية في كل ضغطة، وده اللي Lighthouse مش بيكشفه على شاشتك لأنه بيقيس Lab Data بس.
سيناريو حقيقي بالأرقام
متجر إلكتروني بـ React 18، LCP 1.4s و CLS 0.05 — Lighthouse 96/100. لكن الـ p75 INP من Chrome UX Report عند 612ms. النتيجة: نسبة التحويل في الموبايل 1.8% مقابل 3.2% على متجر منافس INP عنده 180ms. الفرق في الإحساس بيتحوّل لفلوس مباشرة، خصوصًا في مرحلة الـ checkout اللي كل ضغطة فيها مهمة.
السبب الحقيقي: Long Tasks على الـ Main Thread
JavaScript في المتصفح بيشتغل على thread واحد. لما الزائر يضغط زر، المتصفح محتاج: (1) ينفّذ event handler. (2) يحدّث الـ DOM. (3) يرسم الإطار التالي. لو أي خطوة فيهم عدّت 50ms، بقت "Long Task" والإطار اللي الزائر مستنّيه بيتأخّر.
تخيّل طاهي المطعم بيشتغل لوحده في مطبخ صغير. لو طلب واحد طلب منه يقطّع 5 كيلو بصل قبل ما يبدأ يحضّرله أكلته، اللي وراه كلهم بيستنّوا. الـ Main Thread نفس الفكرة: مهمة واحدة طويلة بتعطّل كل اللي وراها — بما فيهم استجابة كليكتك. الحل مش "تخلّص أسرع"، الحل تقسّم المهمة وتدّي الطاهي فرصة يخدم اللي مستنيين.
علميًا: المتصفح بيحاول يرسم 60 frame في الثانية، يعني عنده budget قدره 16.6ms لكل frame. أي مهمة بتعدّي ده بتأكل من الـ budget. لمّا تطول لـ 50ms أو أكتر، الـ Long Tasks API بترصدها كـ blocking task، والـ INP بيرتفع.
الحل: 4 تكنيكات قابلة للنسخ
1. scheduler.yield() لتقسيم المهام
// قبل: مهمة واحدة 380ms
button.addEventListener("click", () => {
const data = processItems(items); // 380ms
renderTable(data); // 40ms
updateAnalytics(data); // 20ms
});
// بعد: نتنازل للمتصفح بين كل خطوة
button.addEventListener("click", async () => {
const data = processItems(items);
await scheduler.yield(); // أتاحة فرصة لرسم الإطار
renderTable(data);
await scheduler.yield();
updateAnalytics(data);
});