Производительность фронтенд-приложений напрямую коррелирует с коэффициентом конверсии, глубиной взаимодействия и позициями в поисковой выдаче 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 (Расчет медианного прогона / Median Run) |
+-------------------------------------------------------------------------+
|
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: Green PR Check: Red / Block Merge
Показатели в норме Деградация LCP на 400ms (> 2.5s)Определение целевых URL, профиля мобильной/десктопной эмуляции и привязки к старту production-сервера.
Фиксация максимального размера файлов бандла JS, CSS, веб-шрифтов и количества сетевых запросов.
Задание предельных значений: LCP <= 2.5s, CLS <= 0.1, Total Blocking Time <= 200ms в секции assert.
Настройка трехкратного прогона для фильтрации аппаратных всплесков задержек в инфраструктуре CI.
Интеграция lhci autorun в конвейер Pull Request с блокировкой слияния кода при регрессии скоров.
Почему при автоматизированном аудите производительности в CI/CD метрика Total Blocking Time (TBT) используется как синтетический эквивалент полевой метрики Interaction to Next Paint (INP)?
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', // или 'mobile' для строгой проверки 4x CPU Throttling
throttlingMethod: 'simulate',
// Отключение аудитов, нерелевантных для локального тестового сервера
skipAudits: ['uses-http2', 'is-on-https'],
},
},
assert: {
// Использование рекомендованного базового набора правил
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 до 1)
'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 (Interaction to Next Paint) оценивает задержку реальных пользовательских взаимодействий. В коде компонентов мы используем стандартный браузерный интерфейс PerformanceObserver для профилирования длинных задач (Long Tasks) и фиксации задержек обработки событий ввода.
/**
* Инициализация мониторинга 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;
};
// Логируем взаимодействия с задержкой выше критического порога 200ms
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);
}Какая конфигурация запуска Lighthouse CI в облачном раннере GitHub Actions обеспечивает максимальную защиту от ложноположительных падений сборки?
Аудит dev-сборки Next.js/Vite полностью искажает метрики: отсутствие минификации JS, выключенный Tree-Shaking, генерация тяжелых source-maps и накладные расходы React Development Mode замедляют LCP и TBT в 3–5 раз, приводя к ложным фейлам.
# ❌ Фатальная ошибка: запуск lhci поверх dev-сервера
startServerCommand: "pnpm run dev"
# ✅ Строго production-окружение: оптимизированная компиляция и минификация
startServerCommand: "pnpm run start" # Предварительно выполнен pnpm run buildВиртуальные машины в облачных раннерах (GitHub Actions, GitLab CI) разделяют ресурсы процессора с другими контейнерами. При единичном запуске (numberOfRuns: 1) случайный скачок нагрузки на хост-машине увеличивает LCP на 800 мс, вызывая падение пайплайна.
// ❌ Хрупкая конфигурация: подвержена шуму виртуализации
numberOfRuns: 1,
// ✅ Надежная конфигурация: медианная фильтрация по 3–5 прогонам
numberOfRuns: 3, // LHCI автоматически выбирает медианный по TBT/LCP прогонМетрика First Input Delay (FID) измеряла задержку только первого взаимодействия и не учитывала время выполнения самого обработчика события и отрисовку кадра. INP оценивает все клики и нажатия клавиш на протяжении всей сессии. Оптимизация только первого ввода оставляет интерфейс неотзывчивым на последующих шагах.
// ❌ Устаревший фокус: проверка только начальной загрузки
// ✅ Современный стандарт: профилирование времени отклика на все действия (INP <= 200ms)
// Разбиение длинных синхронных циклов через scheduler.yield() или requestAnimationFrame