Продуктивність фронтенд-застосунків безпосередньо впливає на коефіцієнт конверсії, залученість користувачів та позиції в пошуковій видачі Google (Core Web Vitals як офіційний фактор ранжування). Проте оптимізація швидкості часто є реактивною: команди виявляють деградації вже після потрапляння коду в продакшн.
Ручний замір швидкодії у браузері страждає від високої варіативності: розширення, кешування, фонові процеси ОС та коливання потужності процесора викривляють метрики. Надійний контроль вимагає інтеграції автоматизованого синтетичного аудиту продуктивності безпосередньо в конвеєр неперервної інтеграції (CI/CD).
Lighthouse CI (@lhci/cli) надає стандартизовану інфраструктуру:
Lighthouse CI оркеструє життєвий цикл від локальної компіляції до збору трейсів продуктивності та перевірки граничних умов.
+-------------------------------------------------------------------------+
| GitHub Actions Runner (Ubuntu Container) |
+-------------------------------------------------------------------------+
|
v 1. Build & Collect (lhci collect)
+-------------------------------------------------------------------------+
| Next.js 15 / React Production Build Server |
| pnpm build && pnpm start (Port 3000) |
+-------------------------------------------------------------------------+
|
v CDP (Chrome DevTools Protocol)
+-------------------------------------------------------------------------+
| Headless Chrome with Throttling Profile |
| - CPU Slowdown: 4x (Емуляція мобільного процесора середнього рівня) |
| - Network Throttling: Fast 4G (RTT 150ms, Throughput 1.6 Mbps) |
| - Viewport: Mobile (360x640) or Desktop (1366x768) |
| - Number of Runs: 3 (Розрахунок репрезентативного медіанного прогону) |
+-------------------------------------------------------------------------+
|
v 2. Raw Trace & Performance Metrics
+-------------------------------------------------------------------------+
| Core Web Vitals Extractor |
| |
| LCP (Largest Contentful Paint) <= 2.5s -> [Pass / Fail] |
| CLS (Cumulative Layout Shift) <= 0.10 -> [Pass / Fail] |
| INP (Interaction to Next Paint) <= 200ms -> [Pass / Fail] |
| TBT (Total Blocking Time) <= 200ms -> [Pass / Fail] |
+-------------------------------------------------------------------------+
|
v 3. Assertions Engine (lhci assert)
+-------------------------------------------------------------------------+
| Resource Budget Validation (budget.json) |
| |
| Total JS Bundle Size <= 180 KB -> [Pass / Fail] |
| Third-party Scripts <= 50 KB -> [Pass / Fail] |
+-------------------------------------------------------------------------+
|
+------------------+------------------+
| |
[PASS] [FAIL]
v v
PR Check: Зелений PR Check: Червоний / Блокування
Метрики в межах SLA Деградація LCP на 400ms (> 2.5s)fetchpriority="high".min-height, aspect-ratio) під динамічні рекламні банери або вебшрифти.lighthouserc.js#Конфігураційний файл налаштовує три етапи: collect (збір даних), assert (валідація порогових значень) та upload (збереження звітів):
module.exports = {
ci: {
collect: {
// Запуск production-сервера перед початком аудиту
startServerCommand: 'pnpm run start',
startServerReadyPattern: 'ready on http://localhost:3000',
startServerReadyTimeout: 60000,
url: [
'http://localhost:3000/',
'http://localhost:3000/catalog',
'http://localhost:3000/pricing',
],
// 3 запуски для обчислення стабільного медіанного значення
numberOfRuns: 3,
settings: {
// Профіль емуляції
preset: 'desktop',
throttlingMethod: 'simulate',
// Пропуск перевірок, неактуальних для тестового контуру
skipAudits: ['uses-http2', 'is-on-https'],
},
},
assert: {
// Базовий рекомендований набір правил Google
preset: 'lighthouse:recommended',
assertions: {
// 1. Core Web Vitals: Строгі порогові значення
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 200 }],
// 2. Інтегральні оцінки категорій (від 0.0 до 1.0)
'categories:performance': ['error', { minScore: 0.90 }],
'categories:accessibility': ['error', { minScore: 0.95 }],
'categories:best-practices': ['error', { minScore: 0.95 }],
'categories:seo': ['error', { minScore: 0.95 }],
// 3. Бюджети ресурсів
'resource-summary:script:size': ['error', { maxNumericValue: 180000 }], // 180 KB
'resource-summary:total:size': ['error', { maxNumericValue: 600000 }], // 600 KB
'dom-size': ['error', { maxNumericValue: 1200 }],
},
},
upload: {
// Збереження HTML-звітів у тимчасове сховище або власний LHCI сервер
target: 'temporary-public-storage',
},
},
};budget.json#Детальне бюджетування ресурсів обмежує вагу файлів за типами асетів та сторонніми бібліотеками:
[
{
"path": "/*",
"timings": [
{
"metric": "interactive",
"budget": 3000
},
{
"metric": "first-contentful-paint",
"budget": 1200
}
],
"resourceSizes": [
{
"resourceType": "script",
"budget": 180
},
{
"resourceType": "stylesheet",
"budget": 45
},
{
"resourceType": "image",
"budget": 250
},
{
"resourceType": "font",
"budget": 80
},
{
"resourceType": "third-party",
"budget": 50
},
{
"resourceType": "total",
"budget": 600
}
],
"resourceCounts": [
{
"resourceType": "third-party",
"budget": 5
},
{
"resourceType": "script",
"budget": 10
}
]
}
]INP відображає чуйність інтерфейсу під час взаємодії користувача. У коді компонентів використовується браузерний API PerformanceObserver для відстеження тривалих завдань:
/**
* Ініціалізація спостереження за Core Web Vitals та затримками взаємодій
*/
export function registerPerformanceObservers(): void {
if (typeof window === 'undefined' || !('PerformanceObserver' in window)) {
return;
}
try {
// 1. Відстеження Largest Contentful Paint (LCP)
const lcpObserver = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
if (lastEntry) {
console.info(`[Web Vitals] LCP: ${lastEntry.startTime.toFixed(2)}ms`, lastEntry);
}
});
lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
// 2. Відстеження Cumulative Layout Shift (CLS)
let clsValue = 0;
const clsObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
const layoutShift = entry as PerformanceEntry & { hadRecentInput?: boolean; value: number };
// Виключаємо зсуви, викликані безпосередніми діями користувача
if (!layoutShift.hadRecentInput) {
clsValue += layoutShift.value;
console.info(`[Web Vitals] Cumulative CLS: ${clsValue.toFixed(4)}`);
}
}
});
clsObserver.observe({ type: 'layout-shift', buffered: true });
// 3. Відстеження Interaction to Next Paint (INP) через Event Timing API
const inpObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
const eventEntry = entry as PerformanceEntry & {
duration: number;
processingStart: number;
processingEnd: number;
interactionId?: number;
};
// Логування подій, затримка яких перевищує критичні 200 мс
if (eventEntry.duration > 200) {
console.warn(
`[Web Vitals Slow INP] Event "${eventEntry.name}" took ${eventEntry.duration.toFixed(2)}ms`,
{
inputDelay: eventEntry.processingStart - eventEntry.startTime,
processingTime: eventEntry.processingEnd - eventEntry.processingStart,
presentationDelay: eventEntry.duration - (eventEntry.processingEnd - eventEntry.startTime),
}
);
}
}
});
inpObserver.observe({ type: 'event', buffered: true, durationThreshold: 40 });
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Unknown performance observer error';
console.error(`Failed to register PerformanceObserver: ${message}`);
}
}performance-gate.yml#Пайплайн збирає production-бандл застосунку, запускає @lhci/cli та створює звіт у Pull Request:
name: Performance Quality Gate (Lighthouse CI)
on:
pull_request:
branches: [main]
jobs:
lhci:
name: Lighthouse CI Web Vitals Audit
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Next.js production bundle
run: pnpm run build
- name: Run Lighthouse CI
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
run: |
pnpm exec lhci autorun
- name: Post LHCI status comment
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = './.lighthouseci/manifest.json';
if (fs.existsSync(path)) {
const manifest = JSON.parse(fs.readFileSync(path, 'utf8'));
console.log('Lighthouse CI runs completed:', manifest.length);
}Аудит у dev-режимі повністю викривляє результати: відсутність мініфікації JS, вимкнений tree-shaking та накладні витрати React DevTools уповільнюють LCP і TBT у 3–5 разів, гарантуючи хибні падіння тестів.
# ❌ ПОМИЛКА: запуск lhci поверх dev-сервера
startServerCommand: "pnpm run dev"
# ✅ ПРАВИЛЬНО: аудит строго оптимізованої production-збірки
startServerCommand: "pnpm run start" # Після виконання pnpm run buildВіртуальні машини в хмарних середовищах (GitHub Actions) ділять ресурси процесора з іншими контейнерами. При одному прогоні (numberOfRuns: 1) випадковий стрибок навантаження хост-машини штучно збільшить LCP, що спричинить хибний фейл.
Рішення: встановлювати numberOfRuns: 3 або 5. LHCI автоматично відбирає та оцінює медіанний прогін.
Метрика First Input Delay (FID) враховувала лише затримку першої взаємодії користувача зі сторінкою, ігноруючи час виконання обробника події. INP оцінює чуйність усіх дій протягом усієї сесії. Оптимізація лише першого вводу залишає застосунок повільним на наступних кроках.
Визначення цільових маршрутів, профілю емуляції та команди запуску production-сервера.
Встановлення обмежень на вагу JS-бандлів, таблиць CSS, вебшрифтів та сторонніх скриптів.
Завдання граничних значень: LCP <= 2.5s, CLS <= 0.1, Total Blocking Time <= 200ms у блоці assert.
Налаштування 3-5 повторень аудиту для усунення віртуалізаційного шуму хмарних ранерів CI.
Інтеграція lhci autorun у робочий процес PR з автоматичним блокуванням злиття при деградації швидкодії.