В 2026 году веб-производительность — это уже не просто "сделать страницу быстрой", а постоянный инженерный процесс с принятием обоснованных решений. Вы развёртываете интернет-магазин, оценка Lighthouse падает с 92 до 68, коэффициент конверсии снижается с 3,2% до 2,7% — но никто это не замечает, потому что мониторинг ограничивается проверкой "сервер упал?". Привязка performance budget к механизму принятия решений означает: поймать регрессию до развёртывания, оценить каждый коммит по порогам LCP/TBT/CLS и подать данные RUM в pipeline атрибуции. В этой статье мы покажем, как интегрировать Lighthouse CI, синтетический мониторинг, RUM и систему алармов в единую архитектуру.
Что Такое Performance Budget и Почему его Должна Контролировать Система, а не Человек
Performance budget — это числовые пороги на ресурсы и метрики производительности на странице: максимальный размер JavaScript-бандла (например, 200 КБ gzip), максимальный TBT (Total Blocking Time, 200 мс), максимальный LCP (Largest Contentful Paint, 2,5 сек). Эти цифры не произвольны — пороги Core Web Vitals от Google определяют полосу "хорошо", но вам нужно вывести более острые лимиты из данных о конверсии вашего сектора.
Классический сценарий "Lighthouse 95 в dev-среде, 72 в production" возникает по этим причинам: синтетический тест работает в лабораторных условиях (быстрая сеть, пустой кеш, одна загрузка страницы), а RUM измеряет реального пользователя на 3G с полным кешем и разными типами навигации. Разница нормальна, но обе метрики нужно отслеживать. Lighthouse CI ловит регрессии размера бандла в каждом PR, RUM показывает production-реальность вроде "у 22% мобильных пользователей LCP превышает 4 секунды". Если budget определён только как "score выше 75", вы можете добавить 100 КБ в бандл, увеличить оценку с 74 до 76 — страница утяжелится, но score зелёный. Поэтому budget должен быть двойным: метрика-базированный (LCP, TBT, CLS) и ресурс-базированный (JS, CSS, размер изображений в МБ).
Ещё один момент: для enforcing budget'а недостаточно ручного код-ревью. "Проверим performance при ревью" не масштабируется при 20 PR/день. Система должна измерять, система должна fail'ить, люди должны только понимать причину.
Lighthouse CI: Performance Gating Для Каждого Коммита
Lighthouse CI автоматически запускает Lighthouse-аудит на каждом коммите или PR и отправляет результаты в GitHub или внутренний dashboard. Интеграция в CI-pipeline:
# .github/workflows/lighthouse-ci.yml
name: Lighthouse CI
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci && npm run build
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
В конфиге .lighthouserc.json вы определяете budget'ы:
{
"ci": {
"collect": {
"url": ["http://localhost:3000/"],
"numberOfRuns": 3
},
"assert": {
"preset": "lighthouse:recommended",
"assertions": {
"total-byte-weight": ["error", { "maxNumericValue": 512000 }],
"total-blocking-time": ["error", { "maxNumericValue": 200 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"categories:performance": ["error", { "minScore": 0.85 }]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
При такой настройке, если PR добавляет 50 КБ лишнего JavaScript и TBT превысит 200 мс, CI упадёт и merge заблокируется. В проектах Roibase на Headless Architecture этим подходом мы сократили среднее TBT с 2190 мс до 200 мс — каждая библиотека тестировалась против budget перед добавлением.
Ограничения Lighthouse CI и Структурные Решения
Lighthouse CI выполняет синтетический тест: фиксированная пропускная способность (Moto G4, slow 4G), фиксированное замедление CPU (4x), одна страница. Реальный пользователь находится на другом устройстве, проходит разные сценарии (товар → корзина → оформление), видит варианты A/B-тестов. Поэтому рассматривайте Lighthouse CI как минимальный порог — если pass, развёртывание возможно, но pass не означает идеальное production-состояние. RUM нужен для измерения production-реальности.
RUM (Real User Monitoring): Превращение Production-Реальности в Данные Для Решений
RUM собирает метрики от реальных пользователей: Navigation Timing API, PerformanceObserver, CrUX. Сборку выполняет vendor (Speedcurve, Sentry Performance, Cloudflare Web Analytics) или собственный stack (библиотека web-vitals + BigQuery).
Минимальная интеграция web-vitals:
// app.js
import { onCLS, onFID, onLCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
navigationType: metric.navigationType,
page: window.location.pathname,
deviceType: /mobile/i.test(navigator.userAgent) ? 'mobile' : 'desktop'
});
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/vitals', body);
} else {
fetch('/api/vitals', { method: 'POST', body, keepalive: true });
}
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
Загрузите эти данные в BigQuery, затем объедините их с данными атрибуции маркетинга через dbt:
-- models/performance_impact.sql
WITH vitals AS (
SELECT
session_id,
AVG(CASE WHEN metric_name = 'LCP' THEN value END) AS avg_lcp,
AVG(CASE WHEN metric_name = 'CLS' THEN value END) AS avg_cls
FROM {{ ref('raw_vitals') }}
GROUP BY session_id
),
conversions AS (
SELECT session_id, revenue, converted
FROM {{ ref('ga4_sessions') }}
)
SELECT
CASE
WHEN v.avg_lcp <= 2500 THEN 'good'
WHEN v.avg_lcp <= 4000 THEN 'needs_improvement'
ELSE 'poor'
END AS lcp_band,
COUNT(*) AS sessions,
SUM(c.converted) AS conversions,
SAFE_DIVIDE(SUM(c.converted), COUNT(*)) AS cvr
FROM vitals v
LEFT JOIN conversions c USING(session_id)
GROUP BY lcp_band;
Эта таблица покажет вам конкретику вроде "при LCP ≤ 2,5 сек CVR 3,4%, при LCP > 2,5 сек CVR 2,1%". Когда вы рапортируете это CMO, абстрактный запрос "давайте оптимизируем производительность" превращается в конкретику: "если LCP ≤ 2,5 сек, +18K$ ежемесячного дохода".
Регрессионные Alarmy в Slack/PagerDuty: Автоматическое Обнаружение
После сбора RUM-данных настройте автоматическое обнаружение регрессии. Если 7-дневное среднее LCP было 2,2 сек, а сегодня поднялось до 3,1 сек — это регрессия развёртывания или проблема CDN. Ловить это через ручной мониторинг неправильно — нужна автоматическая триггеризация.
Metric-Based Alerting с DataDog
DataDog автоматически парсит RUM-метрики и выполняет anomaly detection. Пример конфигурации монитора:
{
"name": "LCP Regression - Desktop",
"type": "metric alert",
"query": "avg(last_1h):avg:rum.largest_contentful_paint{device:desktop} > 2500",
"message": "LCP desktop за последний час превысил 2500 мс. Последнее развёртывание: {{deploy.id}}. @slack-perf-alerts @pagerduty",
"tags": ["service:ecommerce", "env:production"],
"thresholds": {
"critical": 2500,
"warning": 2200
}
}
Когда аларм срабатывает, уведомление падает в Slack, incident открывается в PagerDuty, on-call разработчик получает уведомление. ID развёртывания в сообщении (поступает из CI-pipeline) позволит за 30 секунд найти проблемный коммит.
Forwarding Lighthouse CI Threshold Failure as Alarm
Некоторые команды отправляют fail Lighthouse CI не только как блок PR, но и как Slack-уведомление:
# .github/workflows/lighthouse-ci.yml (дополнительный step)
- name: Notify Slack on Failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Lighthouse CI FAILED on PR #${{ github.event.pull_request.number }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Performance budget exceeded*\nPR: <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>\nBranch: `${{ github.head_ref }}`"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_PERF }}
Так разработчик увидит красный флаг в CI и получит Slack-сообщение одновременно — внимание привлечётся незамедлительно.
Привязка Budget'ов к Системе Feature Flags
Некоторые feature'ы наследственно тяжёлые: live chat (80 КБ JS), персонализация (150 КБ + runtime), видеоплеер (200 КБ). Вместо раскатывания всем пользователям, вы можете тестировать на сегменте с запасом по performance-budget (например, desktop + быстрое соединение), постепенно расширять.
В LaunchDarkly или собственной системе flag'ов определите правила:
// featureFlags.js
import { getConnectionSpeed } from './utils';
export function shouldEnableChatWidget(user, vitals) {
const is4G = getConnectionSpeed() === '4g';
const goodLCP = vitals.lcp < 2000;
return is4G && goodLCP && user.tier === 'premium';
}
Решение "добавим chat widget" не несёт риск "LCP всех пользователей вырастет на 300 мс" — открывается только для сегмента, собираются RUM-данные, измеряется CVR-влияние, потом full rollout или откат. При обсуждении с маркетингом и product вы привносите числа: "Chat widget повышает CVR на 0,4%, но LCP переходит на 2,8 сек — нетто +8K$/месяц доход, но UX падает. Как идти дальше?"
Performance Budget на Headless Commerce
Headless commerce (Shopify Hydrogen, Next.js + Shopify API) обычно быстрее Liquid-тема, потому что JS-контроль в ваших руках, возможна selective hydration. Но контроль на вас — регрессия тоже на вас. Обновление npm-пакета может добавить 70 КБ в бандл.
В рамках Headless Services Roibase мы применяем этот workflow при миграции:
- Baseline: Соберите RUM-данные текущего Liquid-тема (30 дней). Запишите медиану LCP, TBT, CLS.
- Headless Prototype с Lighthouse CI: Каждый коммит должен соответствовать
.lighthouserc.json. Первое развёртывание должно быть на 20% лучше baseline. - A/B на Production: