Подробный кейс: какие метрики были до, что мешало LCP и какие точные изменения в конфигурации изображений и шрифтов дали LCP 0.8s. Практические фрагменты кода и инструкции для повторения.
В середине 2025 года я провёл комплексную оптимизацию публичного сайта на Next.js и добился LCP 0.8s на страницах с большим героем. В лидере перечислены конкретные изменения по изображениям, шрифтам и сборке, которые дали основной выигрыш времени.
Начальный замер и проблема
Исходная страница была создана на Next.js (App Router, версия 13.x/14.x, проект развивался до 2025–2026 годов) с серверным рендерингом (SSR) для маркетинговых страниц. В январе 2025 мы зафиксировали проблемную метрику LCP на ключевой товарной странице — около 2.9–3.3 секунды по Lighthouse и 2.6–3.1 секунды по WebPageTest (реалистичный 4G). FCP и TTFB были в пределах приемлемого: TTFB ~ 200–250 мс при использовании Vercel, FCP ~ 1.0–1.4 с, но именно LCP отставал.
Для измерения и верификации использовались следующие инструменты и дата-источники:
Chrome DevTools Lighthouse (2025-02, версия 11+)
WebPageTest (4G, London и US East, по 9 runs)
Real User Monitoring (RUM) — собственный агент, который собирал LCP события по полю trace (RUM-агрегаты за Q4 2024—Q1 2025)
PageSpeed Insights (API) для проверки в реальном времени
Проблема: LCP рендерился за счёт большого hero-изображения и кастомного веб-шрифта, которые загружались и рендерились поздно. При этом код страницы был относительно лёгким: JavaScript бандлы после минимизации составляли ~120–180 KB (gzipped), но браузер ждал загрузки ресурса hero и ключевых шрифтов до отображения видимого слоя страницы.
Что мешало LCP?
0
Статья была полезной?
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…
Детальный аудит выявил несколько конкретных причин задержки LCP:
Неправильная загрузка hero-изображения: использовался обычный <img> на CDN без preload и без современных форматов (WebP/AVIF). Размер основного изображения был 2200×1200 и весил ~650 KB в JPEG.
Отложенная загрузка шрифтов: кастомный WOFF2 шрифт загружался через @font-face без preload, с font-display: auto, что приводило к блокировке LCP до получения глифов или FOUT/FOIT в некоторых браузерах.
Priority hints отсутствовали: Next.js по умолчанию оптимизирует изображения, но в конкретной реализации hero был загружен как динамический remote-URL — это лишило нас возможности воспользоваться автоматикой оптимизации Next/Image без дополнительной конфигурации.
Preconnect/preload не использовались: к CDN не было preconnect, а критические ресурсы не были явно прелоаднуты.
Встраивание стилей: критические стили были в глобальном бандле CSS, который поступал чуть позже рендеринга.
В сумме все эти факторы давали «узкое место» — браузер рендерил LCP только после загрузки героя и шрифта, задерживая окончательный визуальный ответ страницы.
Изменения в images и fonts
Ключевая гипотеза: если сделать hero-изображение и ключевой шрифт приоритетными — LCP упадёт существенно. Мы внесли изменения в три слоя: сборка/конфигурация Next.js, серверную отдачу ресурсов и HTML-шаблон.
Изображения
Принятые решения и изменения:
Перевёл hero-изображение в AVIF/WebP и сжал до целевого веса ~60–90 KB для десктопа и ~25–40 KB для мобильной версии.
Использовал компонент next/image с атрибутом priority и корректным sizes. Это позволило Next.js генерировать оптимальные srcset и отдавать заранее подготовленную версию.
Добавил <link rel="preload" as="image" href="/path/to/hero.avif" importance="high" imagesrcset="..." imagesizes="..." crossorigin="" для критичного условия, чтобы браузер начал загрузку до JS.
Настроил next.config.js для поддержки AVIF/WebP и правильного кэширования:
Важно: мы использовали статический импорт для ключевого героя (файлы в /public или импортируемые импорты), чтобы Next.js мог встроить оптимизированный blurDataURL и оперативно обслуживать основные размеры.
Шрифты
Шрифты давали значительную фрагментацию рендера. Что сделано:
Ключевой кастомный шрифт конвертирован и выдан в формате WOFF2, нормализован для латиницы/кириллицы с минимальным набором глифов. Получилось сократить вес с 120 KB до ~32 KB (WOFF2 subset).
Добавлен preload для критического шрифта в app/head или в собственном <head> (_document.js/_app.js для pages-router):
Альтернативно, если используется встроенный механизм Next.js (next/font), то применили server-side font loading, чтобы избежать FOIT и давать glyphs раньше. Пример с next/font:
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin', 'cyrillic'],
display: 'swap',
preload: true,
})
export default function RootLayout({ children }) {
return (<html lang="ru" className={inter.className}><body>{children}</body></html>)
}
Если используете кастомные локальные файлы через @next/font/local, устанавливайте preload: true и указывайте font-display: swap в @font-face.
HTML и preload hints
Мы добавили в голову страницы явные подсказки:
<link rel="preconnect" href="https://cdn.example.com" crossorigin> для установления TCP/TLS заранее.
Прелоад критического изображения и шрифта (см. примеры выше).
Минимизация критических стилей: inline несколько строк CSS для hero-layout (фон, высота, шрифтовые переменные), остальные стили загружались асинхронно с помощью next/font и критического CSS.
Результаты до
Параметры «до» замеров, собранные в феврале 2025 (средние значения по 10 тестам WebPageTest и Lighthouse):
Итоговый вес первой загрузки (без кеша): ~1.8 MB, из которых изображения — ~800 KB
RUM по пользователям показывал медиану LCP ~2.8s для посетителей из Европы и ~3.4s для мобильных 3G/4G пользователей из некоторых регионов без CDN preconnect. Проблемы наиболее ярко проявлялись на мобильных устройствах с высокими DPR, где загружался большой ресурс изображения.
после
После внедрения изменений (апрель — май 2025) показатели улучшились существенно. Окончательный набор мер включал: генерацию AVIF, приоритетную загрузку героя, прелоад шрифта, preconnect к CDN, критический inline CSS и удаление ненужных third-party скриптов в critical path. Результаты:
LCP: 0.8s (Lighthouse median после оптимизаций, апрель 2025, desktop и мобильные тесты в 4G). В WebPageTest средний LCP — 0.7–0.9s по 9 runs.
FCP: снизился до ~0.45–0.7s.
TTFB: остался ~180–230 ms (не критично для LCP, но немного улучшился благодаря CDN).
CLS: сохраняется на низком уровне 0.01 — 0.02.
Вес критических ресурсов уменьшился: hero ~60–90 KB (AVIF), шрифт ~32 KB (WOFF2 subset), критический CSS ~3–6 KB inline.
RUM через 30 дней показал медиану LCP 0.95s для всех пользователей и 0.82s для пользователей в приоритетных регионах с современными браузерами. Это позволило поднять Overall Performance score в Lighthouse с ~55 до ~95 точек на тестовой странице.
Главный эффект дал не один хитрый трюк, а набор простых действий: оптимальные форматы изображений, прелоад ключевых ресурсов и уменьшение критического пути рендеринга.
Как повторить?
Привожу упрощённый чек-лист и конкретные шаги для повторения оптимизации на вашем Next.js-проекте. Эти шаги подходят для App Router и Pages Router, небольшие правки зависят от структуры.
Измерьте текущее состояние.
Соберите Lighthouse/LR и WebPageTest (4G). Пример команды для Lighthouse CI: lighthouse https://example.com --output=json --output-path=./lh-report.json.
Активируйте RUM и соберите LCP за 1–2 недели, чтобы видеть реальные паттерны.
Оптимизируйте hero-изображение.
Создайте AVIF/WebP версии и используйте next/image с атрибутом priority.
Для динамических remote-URL настройте remotePatterns в next.config.js и убедитесь, что Next.js может оптимизировать изображение.
Создайте subset WOFF2 для нужных языков, добавьте <link rel="preload" as="font" href="/fonts/...woff2" type="font/woff2" crossorigin>.
Используйте display: swap и/или next/font с preload: true.
Минимизируйте критический CSS.
Inline несколько правил для вышеэкрана (hero layout) и отложите загрузку остального CSS.
Удалите или отложите third-party скрипты.
Перенесите аналитические/виджет-скрипты в deferred/async или загружайте после LCP через setTimeout/idle callback.
Проверьте конфигурацию сервера/CDN.
Добавьте preconnect для CDN, установите длительный cache-control для статических AVIF/WOFF2.
Тестируйте повторно и следите за RUM.
Проведите серию WebPageTest и Lighthouse (повторите 5–10 раз) и анализируйте медиану LCP.
Соберите RUM за 1–2 недели для проверки в реальных условиях.
Полезные примерные команды и Snippets:
# WebPageTest API пример (curl)
curl -X POST "https://www.webpagetest.org/runtest.php" \
-F "url=https://example.com" \
-F "k=YOUR_API_KEY" \
-F "location=ec2-lon_mobile" \
-F "runs=9"
# Lighthouse CLI
lighthouse https://example.com --mobile --output html --output-path=./lh-report.html
Резюме по приоритетам для быстрого выигрыша:
Прелоад ключевого изображения и ключевого шрифта.
Использование современных форматов изображений (AVIF/WebP) и next/image с priority.
Минимизация критического CSS и удаление render-blocking аналитики.
Контроль веса шрифтов через subset и preload.
Этот кейс также дополняет материалы по производительности на нашем сайте: смотрите смежные рубрики Performance и Frontend для примеров конфигураций и шаблонов оптимизации. Практика показала, что системный подход дает куда больший эффект, чем попытки «улучшить» только JS-бандлы без работы с ресурсами рендера.
Если необходимо, могу прислать конкретный diff для вашего Next.js проекта (next.config.js, head и компонент Image), а также пример CI сценария для автоматической проверки LCP в pull request-ах (Lighthouse CI / PSI).
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…