المقال ده هيخليك تحط بوابة أداء آلية على كل Pull Request. لو زمن تحميل الصفحة (LCP) زاد أو حصل Layout Shift، الـ build بيفشل قبل ما الكود يوصل للمستخدمين.
أتمتة قياس Core Web Vitals بـ Lighthouse CI
المشكلة باختصار
موقعك بيبقى سريع يوم اللي بتطلقه. بعد 3 شهور و40 deploy، نفس الصفحة بقت بتحمّل في 4 ثواني بدل 1.8. مفيش حد شاف التراجع لأنه حصل بالتدريج: صورة زادت 200KB هنا، مكتبة JavaScript اتضافت هناك، خط ويب جديد، وهكذا. Google بتعاقبك في الترتيب، والمستخدم بيقفل التاب قبل ما يخلّص التحميل. الحل مش إنك تقيس يدويًا كل أسبوع — الحل إن الـ CI يرفض أي PR بيخلي الأرقام تسوء.
ما هو Lighthouse CI؟ (بمثال أولًا)
تخيّل إن عندك حارس عند باب الإنتاج. كل PR بيجي الحارس ده بيفتح موقعك بنسخة الكود الجديدة، يقيس 5 أرقام (أشهرهم LCP و CLS و TBT)، ويقارنهم بالحدود اللي انت حطيتها. لو الأرقام فوق الحد، الحارس بيقفل الباب (الـ build بيفشل) والـ PR مبيتدمجش.
بشكل علمي أدق: Lighthouse CI هو أداة رسمية من Google مبنية على Lighthouse (نفس المحرك اللي موجود في Chrome DevTools). بتشتغل headless في بيئة الـ CI، بتعمل audit كامل لكل URL بتديهوله، وبتطبّق assertions على نتائج الـ audit. اللي بيميّزها عن تشغيل Lighthouse يدويًا إنها بتعمل متوسط عدة runs (3 افتراضيًا) علشان تقلل الـ flakiness، وبتخزن النتائج التاريخية علشان تقدر تقارن.
Core Web Vitals — ما تقيسه بالظبط
قبل ما تضبط الـ budgets، لازم تفهم الأرقام بمعناها العملي:
- LCP (Largest Contentful Paint): الوقت من أول ما اليوزر فتح الصفحة لحد ما أكبر عنصر فيها (غالبًا صورة hero أو heading) ظهر. الحد الصحي من Google: أقل من 2.5 ثانية.
- CLS (Cumulative Layout Shift): مجموع الحركات المفاجئة في تخطيط الصفحة أثناء التحميل. تخيّل إنك بتقرا مقال وفجأة إعلان طلع فوقه فدفعه لتحت. ده CLS سيء. الحد: أقل من 0.1.
- INP (Interaction to Next Paint): الوقت بين ضغط اليوزر على زرار ورده الفعل البصري للصفحة. في CI بيتستبدل بـ TBT (Total Blocking Time) كـ proxy لأن INP محتاج تفاعل حقيقي. الحد: TBT أقل من 200ms.
الإعداد في 4 خطوات
- ثبّت الـ CLI:
npm install --save-dev @lhci/cli - أنشئ ملف
lighthouserc.jsonفي جذر المشروع. - ضيف workflow في
.github/workflows/lighthouse.yml. - اعمل PR تجريبي يخلّي LCP يزيد عمدًا، وشوف الـ build بيفشل.
ملف lighthouserc.json — إعداد فعلي
{
"ci": {
"collect": {
"url": [
"http://localhost:3000/",
"http://localhost:3000/blog",
"http://localhost:3000/pricing"
],
"startServerCommand": "npm run start",
"numberOfRuns": 3,
"settings": {
"preset": "desktop",
"throttlingMethod": "simulate"
}
},
"assert": {
"assertions": {
"categories:performance": ["error", {"minScore": 0.9}],
"categories:accessibility": ["error", {"minScore": 0.95}],
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
"cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}],
"total-blocking-time": ["warn", {"maxNumericValue": 200}],
"uses-long-cache-ttl": "off"
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
ليه numberOfRuns: 3؟ لأن Lighthouse نتيجته بتتغيّر ±5% بين run والتاني بسبب network jitter و CPU load في الـ runner. المتوسط بيقلل التذبذب ده. لو عندك مشروع أكبر وبتجرب 10 URLs، زوّدها 5 runs.
GitHub Actions Workflow الكامل
name: Lighthouse CI
on:
pull_request:
branches: [main]
jobs:
lhci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli@0.15.x
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
الـ workflow ده بيشتغل على كل PR تجاه main. بيبني المشروع، يشغّل الـ server محليًا، يقيس الـ URLs التلاتة، ويرجّع تقرير كامل في الـ PR comments.
أرقام فعلية من مشروع Next.js
الأرقام دي من مشروع e-commerce عندنا بـ Next.js 14، متوسط 800 visitor يومي:
- قبل Lighthouse CI: LCP متوسط 3.1 ثانية، تراجعه كان بيتكشف بعد 2-3 أسابيع لما يوزر يشتكي.
- بعد 6 شهور من الأتمتة: LCP متوسط 1.9 ثانية. 14 PR اتوقفوا بسبب تراجع أداء قبل ما يوصلوا production.
- وقت تشغيل الفحص: حوالي 2 دقيقة و40 ثانية لـ 3 URLs × 3 runs.
- تكلفة CI: 0 جنيه (ضمن الـ 2000 دقيقة المجانية في GitHub Actions للمشاريع الخاصة).
Trade-offs — ما تكسبه وما تخسره
بتكسب: بوابة أداء آلية، تقارير تاريخية، رفض الانحدار التدريجي اللي مش حد بياخد باله. بتخسر: وقت CI زيادة (2-4 دقايق لكل PR)، وفي الأول هتقابل false positives بسبب تذبذب الأرقام — ده بيحلّه زيادة numberOfRuns واستخدام aggregationMethod: median. كمان الأرقام اللي بتطلع من الـ runner مش نفس أرقام المستخدم الحقيقي (الـ runner Linux VM على شبكة datacenter)، فخليها مرجع للانحدار، مش بديل لبيانات الـ field من Google Search Console.
متى لا تستخدم Lighthouse CI
لو موقعك خلف authentication ومفيش صفحة عامة جوهرية، الإعداد بيبقى معقد (محتاج puppeteerScript للـ login). لو عندك SPA معتمد كليًا على client-side rendering ومفيش npm run build يطلّع HTML ثابت، النتايج هتبقى مضللة. لو الفريق عنده أقل من 5 مطورين والـ deploy يدوي، npx lighthouse يدوي مرة كل أسبوعين أكفأ من ضبط CI pipeline. كمان للمواقع الداخلية (intranet) اللي الأداء فيها مش ميزة تنافسية، الوقت اللي هتصرفه في ضبط الـ budgets ممكن تصرفه في شغل أهم.
الخطوة التالية
افتح terminal في مشروعك دلوقتي، نفّذ npm install --save-dev @lhci/cli، والصق ملف lighthouserc.json اللي فوق. شغّل npx lhci autorun محليًا قبل ما تكتب الـ workflow — لو طلع معاك أرقام منطقية، انقله لـ GitHub Actions. لو الأرقام وحشة من أول مرة، ده معناه إن عندك تراجع أصلاً مش واخد بالك منه، وده اللي الأداة بتمنعه في المستقبل.