БесплатноБесплатный аудит сайта и кода на 1С-Битрикс при заказе доработки или поддержки

Скорость сайта на Битрикс: что реально влияет на Core Web Vitals и как это исправить

Диагностика Core Web Vitals на сайте 1С-Битрикс: профилировщик, обработчики событий и композитный кэш

Если вы уже разобрались, что такое LCP, CLS и INP, прогнали сайт через PageSpeed Insights и увидели красные цифры — дальше начинается самое сложное: понять, что именно в коде и конфигурации Битрикса даёт эту просадку. Общие советы вида «оптимизируйте картинки» и «включите кэш» здесь не работают, потому что на реальном проекте узкое место почти всегда прячется в чём-то одном конкретном — обработчике события, чужом виджете, незакэшированном запросе к каталогу.

Эта статья — не про основы метрик, а про разбор пяти-шести конкретных технических причин, из-за которых сайт на 1С-Битрикс реально тормозит, с указанием, как их найти профилировщиком и как исправить. Если основы ещё не освоены — сначала прочитайте статью «Core Web Vitals для интернет-магазина», там разобраны сами метрики и общие рычаги, а здесь мы идём сразу в диагностику. Материал собран на опыте наших проектов по оптимизации Core Web Vitals на Битрикс.

Коротко

  • Обработчики событий ядра (OnAfterEpilog и другие), которые выполняют тяжёлый код на каждом хите — частая и незаметная причина медленного TTFB.
  • Синхронные сторонние скрипты и виджеты без async/defer блокируют рендер и напрямую бьют по LCP и INP.
  • Неограниченные CIBlockElement::GetList в шаблонах компонентов — классическая причина тяжёлых запросов на страницах каталога.
  • Стратегия загрузки веб-шрифтов (FOIT/FOUT) определяет, будет ли «прыгать» текст и терять CLS.
  • Композитный кэш — не опция «для галочки»: без него каждая страница без кэша компонентов собирается заново на PHP.

С какой точки вести диагностику

Прежде чем чинить что-то конкретное, разделите два разных вопроса: «медленно грузится HTML» (это про TTFB и бэкенд) и «страница долго становится интерактивной или дёргается при загрузке» (это про фронтенд и рендер). Смешивание этих двух категорий — главная причина, почему разработчики тратят время не на то. Оптимизация JPEG-картинок не спасёт, если узкое место — необработанный обработчик события, съедающий 800 мс на каждом хите ещё до того, как браузер получил первый байт HTML.

Практический порядок:

  1. Снимите полевые данные. Посмотрите реальные метрики пользователей — через Яндекс.Метрику, Google Search Console (отчёт Core Web Vitals) или собственный сбор web-vitals.js. Это покажет, какая метрика реально проблемная — LCP, CLS или INP — и на каких шаблонах (главная, каталог, карточка).
  2. Включите отладочную панель Битрикса. В админке — «Настройки → Инструменты разработчика → Панель веб-мастера», либо константа BX_XML_DEBUG/показ отладочной информации для конкретного пользователя. Панель даёт время выполнения по компонентам, число SQL-запросов и хиты кэша.
  3. Прогоните целевую страницу через профилировщик. Встроенный модуль «Монитор производительности» (в редакциях старше «Старта») или сторонний Xdebug/Blackfire на тестовом окружении — чтобы увидеть, где именно тратится время выполнения PHP.
  4. Проверьте Network-вкладку браузера. Отдельно от бэкенда: какие ресурсы блокируют рендер, когда приходит первый байт (TTFB), когда рендерится LCP-элемент, и что происходит между этими моментами.
  5. Сузьте до конкретной причины. Дальше идите по разделам ниже — они покрывают самые частые конкретные виновники на проектах на Битрикс.
Важно: всегда тестируйте на «холодном» и «тёплом» кэше отдельно. Разница в разы — это нормально и не значит, что сайт «сломан», но означает, что для реальных метрик пользователей критична не столько скорость сборки страницы, сколько частота кэш-промахов на боевом трафике.

Обработчики событий на каждом хите

1С-Битрикс построен на событийной модели: любой модуль может подписаться на системное событие через AddEventHandler и выполнить свой код в определённый момент жизненного цикла хита. Это удобно для разработки, но именно здесь чаще всего прячется просадка TTFB на, казалось бы, «оптимизированном» сайте.

Особенно опасны события, которые срабатывают на каждой странице сайта, независимо от того, какой компонент выводится:

Диагностика простая: в профилировщике или через ручное логирование времени внутри init.php засеките суммарное время выполнения всех обработчиков конкретного события. Если на нейтральной статической странице (например, «О компании») время выполнения PHP заметно больше нуля и не объясняется явным выводом компонентов — почти наверняка виноват один из обработчиков, повешенный на общее событие.

Жизненный цикл хита и обработчики событий Prolog Вывод компонентов Epilog OnAfterEpilog внешний API +800 мс Без обработчика: ~120 мс С тяжёлым обработчиком на каждом хите: ~920 мс
Схема: обработчик, повешенный на общее событие вроде OnAfterEpilog, добавляет своё время выполнения к каждому хиту сайта — даже там, где его функциональность не нужна.

Как чинить. Аудит всех подключённых обработчиков (файл .settings.php модулей и init.php), вынос тяжёлых операций из синхронного пути в очередь (агенты, отложенные задания) и обязательные таймауты на любые внешние HTTP-вызовы внутри обработчиков. Если обработчик нужен только для конкретных страниц — проверяйте контекст ($APPLICATION->GetCurPage() или аналог) и выходите раньше, а не выполняйте логику вслепую на каждом хите.

Чужие include и CModule::IncludeModule в рендере

Второй частый источник просадки — модули и виджеты, которые подключаются прямо в шаблоне сайта или в компоненте через CModule::IncludeModule('modulename') и затем делают собственные запросы к базе или к внешним сервисам синхронно, в основном потоке рендера страницы. Каждый такой вызов — это не просто загрузка файла модуля, а потенциально целая цепочка инициализации: собственные обработчики событий, собственные запросы конфигурации, иногда собственные HTTP-запросы наружу (проверка лицензии, получение баннера, синхронизация настроек).

Особенно заметно это на страницах, где такой инклюд стоит в общем шаблоне (header.php, footer.php) и выполняется буквально на каждой странице сайта — включая те, где виджет никогда не используется. Классический пример — модуль онлайн-консультанта или виджет отзывов, интегрированный не через асинхронный JS-сниппет, а через PHP-компонент с собственной логикой на сервере.

Как найти: в профилировщике ищите записи CModule::IncludeModule с заметным временем выполнения на страницах, где логика этого модуля визуально не нужна. Второй признак — модуль, установленный «для теста» полгода назад и забытый, но чьи файлы всё ещё подключаются в общем шаблоне.

Фикс здесь почти всегда один: перенести интеграцию с уровня PHP-инклюда на уровень клиентского JavaScript-сниппета, загружаемого асинхронно и по возможности отложенно (после первого взаимодействия пользователя или через небольшую задержку после DOMContentLoaded). Серверная часть должна остаться только там, где без неё правда не обойтись — например, для персонализации, зависящей от данных сессии.

Некэшируемые сторонние виджеты и скрипты

Чаты, виджеты отзывов, всплывающие формы захвата, сервисы веб-аналитики сверх стандартных счётчиков — на среднем интернет-магазине таких интеграций набирается пять-семь. Каждая тянет свой JS, часто с CSS и шрифтами, и если хотя бы один из них подключён синхронным тегом <script src="..."> без async или defer в разделе <head>, браузер останавливает парсинг страницы до полной загрузки и выполнения этого файла.

Эффект на метрики конкретный: блокирующий скрипт в <head> откладывает момент, когда браузер вообще начинает рендерить видимый контент — это прямой удар по LCP. Если скрипт при этом внедряет свой блок в DOM после загрузки (баннер, плашка согласия), не зарезервировав для него место заранее через CSS — это удар по CLS: layout «прыгает», когда блок внезапно появляется.

Отдельная категория проблем — виджеты, которые сами по себе выполняют тяжёлую работу в основном потоке JS уже после загрузки: собирают детальную аналитику поведения, перехватывают события скролла и клика без дебаунса. Это не влияет на LCP, зато напрямую бьёт по INP — браузер занят выполнением чужого кода в момент, когда пользователь пытается нажать кнопку «В корзину».

СимптомВероятная причинаЧто проверить
LCP растёт, TTFB в нормеБлокирующий скрипт в <head>Порядок и атрибуты <script> в head.php
CLS скачет на загрузкеВиджет вставляет блок без резерва местаmin-height/aspect-ratio под будущий блок
INP плохой при кликах после загрузкиТяжёлые обработчики в основном потоке JSLong tasks в DevTools Performance
Просадка только на мобильныхВиджет не адаптирован, тяжёлый рендер на слабом CPUПрофиль CPU throttling в Lighthouse

Практическое правило: любой сторонний скрипт, без которого страница функционально работает (чат, аналитика, баннеры), должен загружаться async или defer, а в идеале — с отложенным стартом (через requestIdleCallback, таймер в несколько секунд или загрузку по первому скроллу/клику). Это не требует переписывать сам виджет — только способ его подключения в шаблоне.

Веб-шрифты: FOIT, FOUT и просадка LCP/CLS

Если основной шрифт сайта — не системный, а подключённый веб-шрифт (Google Fonts или собственные .woff2), то стратегия его загрузки напрямую участвует в двух метриках сразу. Браузер по умолчанию (в зависимости от настройки font-display) выбирает одно из двух поведений:

На практике правильная стратегия — компромисс: font-display: swap или optional вместе с тщательно подобранным fallback-стеком (системный шрифт с похожими метриками ширины символов) и, если возможно, предзагрузкой критичного файла шрифта через <link rel="preload" as="font"> с корректным crossorigin.

FOIT vs FOUT: что видит пользователь FOIT пусто (текст скрыт) текст появляется только со шрифтом — LCP поздний FOUT текст сразу (fallback) сдвиг целевой шрифт — LCP ранний swap + подобранный fallback текст сразу плавная замена без сдвига — LCP ранний, CLS в норме
Схема: подобранный fallback-шрифт вместе со swap даёт и ранний LCP, и низкий CLS — в отличие от FOIT (поздний LCP) и «сырого» FOUT (сдвиг layout).

Неограниченные CIBlockElement::GetList в шаблонах

Это, пожалуй, самая известная разработчикам Битрикс причина медленного бэкенда — и всё равно одна из самых частых на реальных проектах, особенно после нескольких лет доработок разными подрядчиками. Проблема в том, что вызов CIBlockElement::GetList (или его D7-аналог через \Bitrix\Iblock\ElementTable::getList) вставляется прямо в файл шаблона компонента или в кастомный component_template.php — то есть выполняется на каждый показ страницы, часто без ограничения количества и без кэширования.

Типичные варианты, которые встречаются на аудитах:

Как найти: в отладочной панели включите вывод SQL-запросов постранично и посмотрите на количество запросов на карточке товара или в подвале каталога. Больше 30–50 запросов на статическую по сути часть страницы — почти всегда сигнал N+1 или неограниченной выборки.

Как чинить. Всегда указывайте явные лимиты (nPageSize в параметрах или limit в D7), используйте фильтр ACTIVE_DATE/ACTIVE для отсечения неактивных элементов на уровне SQL, а не PHP, и оборачивайте такие блоки в кэш компонента или ручной CPHPCache/Bitrix\Main\Data\Cache с тегированной инвалидацией. Для вложенных запросов на свойства — используйте один запрос с GetPropertyValuesArray или JOIN через D7 вместо цикла.

Отсутствующий или сломанный композитный кэш

Композит — штатный механизм Битрикса, который отдаёт статически сохранённую HTML-версию страницы почти мгновенно, а динамические зоны (авторизация, корзина, персональные блоки) подгружает отдельными AJAX-запросами поверх статики. Если основы этого механизма и его связь с кэшированием уже знакомы — подробный разбор уровней кэша есть в статье «Кэширование в 1С-Битрикс»; здесь — только то, что касается конкретно Core Web Vitals.

На реальных аудитах композит часто либо не включён вообще, либо включён, но фактически не работает — и оба случая выглядят одинаково снаружи: страница грузится заметно дольше, чем «мгновенно». Частые причины поломки композита именно в контексте скорости:

Эффект на метрики прямой: без рабочего композита каждый запрос без кэша компонентов — это полная сборка страницы на PHP, что напрямую отражается на TTFB и, соответственно, на LCP (браузер физически не может начать рендер, пока не получит HTML).

Как выглядит диагностика на схеме

Собрав всё описанное выше в единый порядок действий, диагностика узкого места выглядит как последовательность проверок, где каждый шаг отсекает одну категорию причин:

Долгий TTFB? да нет Композит выключен или много SQL/обработчиков Проблема во фронтенде: скрипты, шрифты, CLS Профилировщик → компонент-виновник → GetList, обработчик события или инклюд DevTools Network/Performance → блокирующий скрипт, шрифт, long task Точечный фикс → повторный замер
Схема: диагностика идёт от общего (TTFB, композит) к частному (конкретный компонент, обработчик или ресурс фронтенда), и только на последнем шаге — точечный фикс.

Реализация в 1С-Битрикс: где смотреть в админке

Ключевые точки диагностики и настройки живут в нескольких разделах админки, и полезно держать их адреса под рукой при аудите:

  1. Панель веб-мастера / отладка. «Настройки → Инструменты разработчика → Панель веб-мастера» — включает нижнюю отладочную панель с временем выполнения, числом запросов и хитами кэша по каждому компоненту на странице.
  2. Настройки кэширования. «Настройки → Настройки продукта → Автокэширование» — здесь включается/выключается композит, задаются TTL и управляемый кэш. Именно тут чаще всего находится «композит выключен после последнего обновления».
  3. Список обработчиков событий. Штатного единого списка в интерфейсе нет — обработчики регистрируются в коде (init.php, файлы .settings.php модулей), поэтому аудит здесь — это просмотр кода, а не клик по админке. Это стоит явно проговаривать с разработчиком при передаче проекта на поддержку.
  4. Монитор производительности. В редакциях с этим модулем — отдельный раздел, показывающий динамику времени ответа и число запросов на популярных URL за период, полезен для отслеживания регрессий после релизов.
Панель веб-мастера — время выполнения Компонент Время, мс SQL Кэш catalog.section (список) 42 6 HIT Обработчик OnAfterEpilog (custom) 812 14 MISS Похожие товары (custom template) 340 48 MISS bitrix:menu (шапка) 3 0 HIT Итого на странице 1197 мс, 68 SQL-запросов — 2 компонента без кэша дают 96% времени
Схема: отладочная панель наглядно показывает, что почти всё время съедают два конкретных компонента без кэша — обработчик события и блок «похожих товаров», а не сайт «в целом».

Таблица приоритетов: что чинить в первую очередь

Не все причины одинаково окупаются по времени на фикс. Вот ориентир, с чего начинать при ограниченном бюджете на оптимизацию:

ПричинаЭффект на метрикиСложность фиксаПриоритет
Композит выключен/сломанTTFB, LCP — сильный на всех страницахНизкая-средняяВысокий
Тяжёлый обработчик на общем событииTTFB — сильный на всех страницахСредняяВысокий
Неограниченный GetList в шаблонеTTFB на конкретных страницахНизкаяВысокий
Блокирующий сторонний скриптLCP, INPНизкаяВысокий
Стратегия загрузки шрифтовLCP, CLSНизкаяСредний
Чужой PHP-инклюд в общем шаблонеTTFB на всех страницахСредняя-высокаяСредний

Пошаговый аудит перед фиксом

Чтобы не чинить наугад, полезно провести структурированный аудит перед тем, как трогать код. Вот порядок, который мы используем на проектах:

  1. Соберите список ключевых шаблонов страниц. Главная, категория каталога, карточка товара, корзина, оформление заказа — на каждом типе страницы узкие места могут быть разные.
  2. Прогоните каждый шаблон через отладочную панель. Зафиксируйте суммарное время, число SQL-запросов и список компонентов без кэша (MISS).
  3. Отсортируйте компоненты по вкладу во время ответа. Обычно один-два компонента дают 70–90% суммарного времени — начинайте с них.
  4. Проверьте фронтенд отдельно. Lighthouse в режиме «Mobile» с троттлингом CPU и сети — это ближе к реальному опыту пользователя, чем «Desktop» без ограничений.
  5. Зафиксируйте базовые цифры до фикса. Без цифр «до» невозможно доказать, что конкретное изменение реально помогло, а не просто «показалось быстрее».
  6. Правьте по одному фактору и перемеряйте. Одновременное изменение пяти вещей не даёт понять, какая из них дала эффект, а какая — нет.

Частые ошибки диагностики и фикса

Мини-FAQ. Может ли один медленный обработчик события влиять на метрики страниц, где он логически не нужен? Да, если он подписан на общее событие вроде OnAfterEpilog и не проверяет контекст — тогда он выполняется на каждом хите сайта, независимо от того, какая страница открыта.

Чек-лист перед повторным замером

Перед тем как заявлять об улучшении метрик заказчику или команде, убедитесь, что все пункты закрыты:

  1. Зафиксированы базовые цифры. TTFB, LCP, CLS, INP и число SQL-запросов сняты до начала работ на тех же тестовых условиях.
  2. Композит проверен и работает корректно. Персональные данные не попадают в статическую часть, TTL адекватен трафику.
  3. Обработчики событий на общих событиях аудированы. Тяжёлая логика вынесена из синхронного пути или получила проверку контекста.
  4. Все GetList в кастомных шаблонах ограничены и закэшированы. Нет вложенных запросов в цикле (N+1).
  5. Сторонние скрипты подключены асинхронно. Ничего блокирующего в <head> без явной необходимости.
  6. Стратегия загрузки шрифтов задана осознанно. font-display и подобранный fallback-стек прописаны в CSS.
  7. Замер повторён на тех же условиях. Тот же тип устройства, та же сеть, тёплый и холодный кэш отдельно.

Вывод

Плохие Core Web Vitals на 1С-Битрикс почти никогда не бывают «просто сайт тяжёлый» — за просадкой обычно стоит одна-две конкретные технические причины: обработчик события на общем хуке, незакэшированный GetList, блокирующий сторонний скрипт или сломанный композит. Диагностика через профилировщик и отладочную панель занимает часы, а не недели, и почти всегда даёт понятный список конкретных фиксов вместо расплывчатого «надо оптимизировать». Именно такой точечный подход даёт устойчивый прирост метрик, который не откатывается после следующего релиза.

Частые вопросы

С чего начать диагностику, если PageSpeed показывает низкий балл, а непонятно почему?

Сначала разделите «лабораторные» данные (PageSpeed Insights, Lighthouse) и «полевые» (реальные пользователи, CrUX-отчёт или собственная аналитика Web Vitals). Затем откройте отладочную панель 1С-Битрикс и вкладку профилировщика: посмотрите время выполнения по компонентам и число SQL-запросов на странице. Это сразу показывает, тормозит бэкенд или фронтенд.

Обязательно ли переходить на composite-режим, чтобы улучшить Core Web Vitals?

Не обязательно, но это один из самых быстрых по эффекту шагов: композитный кэш отдаёт статический HTML почти мгновенно, а динамические зоны (корзина, авторизация) догружаются отдельно. Без композита каждый хит без кэша компонентов означает полную сборку страницы на PHP, что напрямую бьёт по LCP и TTFB.

Может ли обработчик события в модуле, который мы даже не трогали, замедлять весь сайт?

Да, и это одна из самых незаметных проблем. Обработчики вешаются на события ядра (OnAfterEpilog, OnBeforeProlog, OnSaleOrderSaved и другие) любым установленным модулем или кастомным кодом, и срабатывают на каждый хит, даже если конкретная страница к ним не относится. Если внутри обработчика есть тяжёлый запрос или внешний вызов — тормозит весь сайт, а не только релевантная функциональность.

Как понять, что именно веб-шрифты дают просадку LCP или CLS?

Если LCP-элемент — это текстовый блок (заголовок, крупный абзац) и он появляется с задержкой или «прыгает» при подгрузке шрифта — проблема в стратегии загрузки шрифтов (FOIT/FOUT). В вкладке Network браузера посмотрите, когда загружается .woff2 относительно рендера текста, и есть ли в CSS `font-display`.

Нужно ли переписывать все CIBlockElement::GetList в шаблонах на D7?

Не обязательно все — переход на D7 сам по себе не ускоряет запрос, если он остаётся тяжёлым и не кэшируется. Приоритет — найти вызовы GetList без ограничения по количеству и без фильтра ACTIVE, обернуть их в кэш и, если нужно, ограничить cnt_total/nPageSize. Миграция на D7 полезна отдельно ради чистоты кода и ORM, но не как единственная мера против медленных запросов.

Сколько сторонних виджетов можно держать на странице без вреда для Core Web Vitals?

Универсального числа нет — важнее не количество, а то, как они подключены. Один синхронный blocking-скрипт от стороннего сервиса в <head> может дать больший урон INP и LCP, чем пять правильно отложенных async/defer-виджетов, подгружаемых после взаимодействия пользователя или через IntersectionObserver.

Поделиться: Telegram ВКонтакте WhatsApp

Хотите узнать, что конкретно тормозит ваш сайт?

Проведём технический аудит Core Web Vitals: профилируем ключевые страницы, найдём конкретные причины просадки LCP, CLS и INP и отдадим приоритизированный план фиксов.

Игорь Воскресенский

Основатель студии B2Bsite. С 2014 года команда разрабатывает и поддерживает интернет-магазины и корпоративные сайты на 1С-Битрикс, настраивает каталоги, умный фильтр и обмен с 1С для среднего и крупного бизнеса.

← Все статьи блога