Понедельник утро. Lighthouse CI фейлится на стейджинг-деплое. Оценка производительности: 76. На прошлой неделе: 96. Никто не пушил код на выходных. Нет обновлений зависимостей. Нет изменений конфига.
Я потратил 8 часов на поиск причины. Вот путь отладки.
Час 1: Очевидные проверки
Первая мысль: нестабильный тест. Lighthouse-оценки варьируются на ±3–5 баллов из-за сетевых условий и нагрузки CPU. Падение на 20 — не вариативность, а регрессия.
Локальный запуск Lighthouse: 94. Достаточно близко к базовому. Не регрессия в коде.
Lighthouse на задеплоенном стейджинг-URL: 76. Совпадает с CI.
Разрыв между локальным и задеплоенным сказал мне: проблема в инфраструктуре, не в коде.
Час 2: Анализ водопада
Chrome DevTools → вкладка Performance на стейджинг-URL. Водопад показал:
- HTML: 180мс (нормально)
- Основной JS-бандл: 120мс (нормально)
- Файл шрифта: 2,100мс (ненормально — обычно 80мс)
- Hero-изображение: 350мс (нормально)
Файл шрифта грузился с другого CDN, чем обычно. Google Fonts через fonts.googleapis.com. Ничего не менялось. Но время ответа — изменилось.
curl URL шрифта с Vercel edge: 2.3 секунды. С локальной машины: 45мс.
Час 3: Шрифтовой крюк
Google Fonts был медленным с Vercel edge. Почему? CI-раннер был в другом регионе, и edge-функция делала кросс-региональный запрос к Google Fonts. Заголовки ответа: cache-control: private, max-age=86400. private означает, что CDN не может кешировать. Только браузер. На свежем запуске Lighthouse (пустой кеш) каждый запрос шрифта идёт на origin.
Фикс: Селф-хостинг шрифтов. Скачать WOFF2-файлы, положить в /public/fonts/, ссылаться через @font-face в CSS.
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-display: swap;
font-weight: 100 900;
}
После деплоя с селф-хостингом: загрузка шрифта с 2,100мс до 40мс. Lighthouse: 88. Лучше — но всё ещё не 96.
Час 5: Изображение, которое не было lazy
Недостаёт 8 баллов. Запустил Lighthouse с Treemap. LCP-элемент — hero-изображение, корректно. Но время LCP — 2.8с, было 1.2с.
Проверил hero <Image>:
<Image src="/hero-bg.webp" alt="..." fill priority sizes="100vw" />
priority — корректно для hero. Тогда почему LCP медленный?
Вкладка Network. Hero-изображение грузится нормально. Но второе большое изображение ниже сгиба тоже загружалось с priority:
// Секция About — ниже сгиба
<Image src="/about-photo.webp" alt="..." width={600} height={800} priority />
Кто-то добавил priority к фото секции About при обновлении контента. Браузер предзагружал два больших изображения одновременно. Hero-изображение конкурировало за bandwidth с about-фото, увеличивая LCP на ~1.5 секунды.
Фикс: Убрать priority с изображения ниже сгиба.
После деплоя: LCP снизился до 1.3с. Lighthouse: 95.
Час 7: Почему CI поймал, а локально — нет
Локальный Lighthouse работает на полной пропускной способности с низкой задержкой. Проблемы шрифта и изображения были скрыты быстрым локальным соединением. CI-раннер симулирует троттлинг мобильного соединения (4G), где конкуренция за bandwidth между двумя priority-изображениями и медленным шрифтом реально имела значение.
Именно поэтому ты запускаешь Lighthouse в CI, а не только локально.
Час 8: Предотвращение в будущем
Три гарда:
- Lighthouse CI budget: фейлит проверку, если производительность падает ниже 90 или LCP превышает 2.5с.
- Правило для
priorityна изображениях: только одно изображение на страницу должно иметьpriority. - Селф-хостинг шрифтов как правило проекта: никаких ссылок на Google Fonts.
Таймлайн
| Время | Действие | Оценка |
|---|---|---|
| 0ч | Заметил фейл CI | 76 |
| 2ч | Определил задержку Google Fonts с edge | 76 |
| 4ч | Селф-хостинг шрифтов | 88 |
| 6ч | Убрал лишний priority с изображения ниже сгиба | 95 |
| 8ч | Добавил CI-бюджеты + правила предотвращения | 95 (защищено) |
Падение на 20 баллов. Две корневые причины, обе невидимые локально. 8 часов на поиск, 10 минут на фикс. Вот так работает отладка производительности.
Видишь регрессии производительности, которые не можешь объяснить? Напиши — я держу Lighthouse 98 на продакшн-сайтах и могу проаудировать твой пайплайн производительности.