{"index":2,"slug":"editorial-2027-12-mechanism-author-manifesto","title":"Web performance budget: как читать LCP, INP и CLS вместе","excerpt":"Разбираем, почему один показатель не объясняет скорость интерфейса, как разделить LCP, INP и CLS и превратить наблюдение в проверяемое действие.","contentHtml":"

Проблема начинается с отчёта «страница медленная». Такой симптом не говорит, что именно увидел пользователь: крупнейший блок появился поздно, клик обработался с задержкой или контент прыгнул под пальцем. Цена ошибки — чинить не тот участок: уменьшить JavaScript, пока главный баннер ждёт шрифт, или ускорить загрузку, оставив интерфейс заблокированным длинной задачей.

\n

Причина — свести LCP, INP и CLS к одному среднему score. Эти метрики отвечают на разные вопросы и требуют разных разрезов данных. LCP описывает появление крупнейшего видимого элемента, INP — задержку взаимодействия до следующей отрисовки, CLS — неожиданный сдвиг уже видимого layout. Сначала определите тип симптома и границу измерения, потом выбирайте изменение в коде.

\n

Тезис: performance budget состоит из нескольких проверяемых границ

\n

Budget — это не красивое число в дашборде. Это набор порогов, сегментов, периода и действий владельца. Для каждого показателя нужно знать, какую часть опыта он описывает, где искать причину и что считать регрессией. Рекомендованные границы Core Web Vitals — LCP до 2500 мс, INP до 200 мс и CLS до 0,1. Они помогают классифицировать опыт, но не доказывают причину.

\n

Для полевых данных используйте percentile, а не только среднее. Например, p75 показывает значение, ниже которого находится 75 процентов наблюдений в выбранном сегменте. Смешивать в одной строке мобильные и десктопные устройства, разные версии и разные периоды нельзя: итог потеряет смысл. В budget явно запишите URL, устройство, соединение, release и окно наблюдения.

\n

Механизм: три метрики — три объекта диагностики

\n

LCP фиксирует момент, когда крупнейший контентный элемент стал видимым в пределах загрузки. На результат влияют TTFB, критический CSS, изображение, шрифт, сеть и работа браузера. Поэтому плохой LCP — сигнал разобрать цепочку, а не команда сразу добавить preload или сжать картинку.

\n

INP оценивает задержку после взаимодействий пользователя. Ищите конкретное interaction, обработчик и long task на main thread. Причиной может быть тяжёлый обработчик, сторонний скрипт, лишняя синхронная работа или слабое устройство. Быстрый LCP не означает, что интерфейс быстро отвечает после ввода.

\n

CLS (Cumulative Layout Shift) отвечает на вопрос: насколько неожиданно сдвигался уже видимый контент? В актуальном определении CLS выбирает самое большое session window — окно, в котором сдвиги идут с интервалами менее секунды и общей длительностью не более пяти секунд, — и суммирует баллы внутри него. Поэтому «сумма всех сдвигов за страницу» — устаревшее упрощение. Типовые источники — изображение без зарезервированных размеров, поздняя реклама, вставленный сверху контент или изменение шрифта. Для разбора нужны layout-shift entries, source-элементы и момент сдвига.

\n
Как переводить симптом в проверку
СимптомВероятная причинаПроверкаДействие
LCP выше 2500 мсTTFB, ресурс, CSS или шрифтelement timing, TTFB, waterfallУскорить критический путь и повторить замер
INP выше 200 мсlong task или тяжёлый handlerinteraction trace и main threadРазбить работу или перенести её после ответа
CLS выше 0,1Нет места под изображение или вставкуSession window, source-элемент и layout traceЗарезервировать размер и стабилизировать layout
Среднее хорошее, p75 плохойДлинный хвост или смешанные сегментыp75 по URL, устройству и releaseРазделить сегменты и назначить владельца
Lab и field расходятсяРазная среда и состав трафикаСопоставить условия запускаНе подменять один тип данных другим
\n

Рабочий пример: сначала зафиксировать контекст

\n

Допустим, в отчёте checkout для мобильного сегмента p75 INP хуже порога, а LCP и CLS проходят. Это учебный payload, а не измерение реального сайта: он показывает, какие поля нельзя потерять при передаче результата между сбором, дашбордом и владельцем.

\n
{\n  'url': '/checkout',\n  'segment': { 'device': 'mobile', 'connection': '4g' },\n  'release': '2027.12.1',\n  'percentile': 75,\n  'metrics': { 'lcpMs': 2180, 'inpMs': 640, 'cls': 0.08 }\n}
\n

Здесь проблема не в общем score: LCP 2180 мс и CLS 0,08 проходят рекомендованные границы, а INP 640 мс — нет. Следующий вопрос — не «как ускорить страницу», а «какое взаимодействие сформировало p75 и чем занят main thread перед следующим кадром».

\n

Для triage можно использовать маленький классификатор. Он не измеряет браузер и не считает p75: функция принимает уже агрегированные значения одного URL и сегмента. Все пороги в коде — проектная копия опубликованных рекомендаций; при изменении официальных границ их нужно обновлять вместе с тестами.

\n
const BUDGET = {\n  lcpMs: { good: 2500, poor: 4000 },\n  inpMs: { good: 200, poor: 500 },\n  cls: { good: 0.1, poor: 0.25 },\n};\n\nfunction classify(value, limits) {\n  if (!Number.isFinite(value) || value < 0) return 'invalid';\n  if (value <= limits.good) return 'good';\n  if (value <= limits.poor) return 'needs-improvement';\n  return 'poor';\n}\n\nfunction classifyWebVitals({ lcpMs, inpMs, cls }) {\n  const status = {\n    lcp: classify(lcpMs, BUDGET.lcpMs),\n    inp: classify(inpMs, BUDGET.inpMs),\n    cls: classify(cls, BUDGET.cls),\n  };\n\n  if (Object.values(status).includes('invalid')) {\n    return { ok: false, reason: 'vital-input-invalid' };\n  }\n\n  const overall = Object.values(status).includes('poor') ? 'poor'\n    : Object.values(status).includes('needs-improvement')\n      ? 'needs-improvement'\n      : 'good';\n\n  return { ok: true, ...status, overall };\n}\n\nconsole.log(classifyWebVitals({ lcpMs: 2180, inpMs: 640, cls: 0.08 }));\n// { ok: true, lcp: 'good', inp: 'poor', cls: 'good', overall: 'poor' }
\n

Классификатор нужен для единого triage, но он не отвечает на вопрос о причине. Улучшение LCP после preload не доказывает, что preload был единственной причиной: могли измениться сервер, кеш или состав трафика. Не превращайте результат функции в автоматический rollback без проверяемой связи с release и без отдельного анализа trace.

\n
\"Матрица
Один score скрывает разные механизмы. Для каждой метрики нужен собственный объект проверки и действие.
\n

Flow: от строки в дашборде к изменению

\n
field data: p75 + сегмент + release\n  -> какая граница нарушена?\n  -> какой объект её объясняет: resource / interaction / shift?\n  -> какой один участок принадлежит владельцу?\n  -> изменение и повторный замер в том же сегменте\n  -> соседние метрики не ухудшились? да: оставить, нет: откатить
\n

Поток разделяет две операции. Метрика говорит, где болит пользовательский опыт. Trace или запись элемента показывает, что проверять в системе. Владелец изменения отвечает за участок кода или доставки, но не может объявить причину доказанной только по совпавшему времени.

\n

Порядок работы

\n
  1. Определите URL, сегмент, percentile и окно наблюдения. Не сравнивайте p75 мобильного трафика со средним по всем устройствам.
  2. Для плохого LCP найдите кандидат и разделите TTFB, загрузку ресурса и отрисовку. Для INP найдите interaction и разложите задержку на input, processing и presentation. Для CLS найдите session window и source-элементы.
  3. Сформулируйте один budget на релиз и один диагностический сигнал. Не блокируйте сборку по метрике, которую CI не может воспроизвести.
  4. Сопоставьте лабораторные и полевые данные отдельно. Lighthouse удобен для воспроизводимого CI, field data показывает реальное разнообразие сети и устройств.
  5. Измените один тяжёлый участок: критический ресурс, обработчик, размеры изображения или резервирование места. Зафиксируйте владельца и путь отката.
  6. Повторите измерение тем же сегментом и окном. Улучшение LCP не закрывает автоматически INP и CLS.
\n

Ограничения и критерий готовности

\n

Локальный запуск классификатора не является field evidence. Performance Timeline даёт браузерные примитивы для доступа к entries и PerformanceObserver, но не проверяет вашу sampling-логику, backend-агрегацию или дашборд. Полевой INP может отсутствовать, если на странице не было подходящего взаимодействия. LCP зависит от эвристик и прекращает поиск кандидатов после определённых действий пользователя; bfcache и same-document navigation требуют отдельной обработки.

\n

Оптимизация одной метрики может ухудшить другую. Сжатие изображения уменьшает передаваемый объём, но может добавить CPU-декодирование или изменить момент отрисовки. Разделение JavaScript способно помочь INP, но добавить запросы и повлиять на LCP. Резервирование места снижает CLS, но не исправляет медленный ресурс. Рядом с Core Web Vitals проверяйте размер ресурсов, long tasks, ошибки и пользовательский сценарий.

\n

Готовность наступает, когда для выбранного URL есть p75 LCP, INP и CLS по явно названным сегментам; для каждой плохой строки указан кандидат, interaction или source-элемент; изменение повторено тем же способом; соседние метрики не ухудшились. Если одного поля нет, вывод нужно назвать неполным, а не превращать его в общий score.

\n

Проверяемые источники

"}