Композитный сайт в Битрикс — ускоряем загрузку в 10 раз

Разбираем технологию композитного сайта: как работает, когда применять, что может сломаться. Пошаговая настройка и отладка проблем.

Дмитрий Мещеряков
Дмитрий Мещеряков
📅 7 сентября 2026 г.📖 7 мин чтения

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

Поэтому статья про композит — это не «как включить», а «что проверить перед тем, как включать».

Что такое композитный сайт

Композит — это технология агрессивного кэширования в Битрикс. Вместо генерации страницы на каждый запрос, сервер отдаёт готовый HTML-файл напрямую из nginx, минуя PHP.

Результат: время ответа падает с 200–500ms до 5–20ms.

💡 Совет

Когда использовать: каталоги, карточки товаров, статьи, лендинги — любые страницы с редко меняющимся контентом. Не подходит для корзины, личного кабинета и других персонализированных страниц.

Как это работает

  1. Первый посетитель открывает страницу — PHP генерирует HTML
  2. Битрикс сохраняет HTML в файл /bitrix/html_pages/...
  3. Следующие посетители получают файл напрямую от nginx
  4. Динамические блоки (корзина, авторизация) подгружаются через AJAX
text
Без композита:
Браузер → nginx → PHP → MySQL → PHP → nginx → Браузер
         [=================== 300ms ===================]
С композитом:
Браузер → nginx → HTML-файл → Браузер
         [======= 15ms =======]

Шаг 1: Включение композита

Админка → Настройки → Настройки продукта → Автокеширование → Композитный сайт.

php
<?php
// Или через код в init.php
\Bitrix\Main\Composite\Page::getInstance()->setEnabled(true);

Настройки nginx

Отдачу статики настраивает nginx. Логика простая: собрать путь к возможному файлу композита и сбросить его в пустую строку в каждом случае, когда отдавать статику нельзя. try_files с пустой переменной просто уходит в фолбэк на PHP.

nginx
# /etc/nginx/sites-available/site.conf

# Путь к кэшированному файлу композита
set $composite_file /bitrix/html_pages/$http_host$request_uri/index.html;

# Любое из условий ниже отключает отдачу статики
if ($request_method != GET)            { set $composite_file ""; }
if ($cookie_BITRIX_SM_LOGIN != "")     { set $composite_file ""; }
if ($cookie_BITRIX_SM_SALE_UID != "")  { set $composite_file ""; }
if ($arg_clear_cache = "Y")            { set $composite_file ""; }

location / {
    try_files $composite_file @bitrix;
}

location @bitrix {
    fastcgi_pass php-fpm;
    # ... остальные настройки PHP
}
⚠️ Важно

Отдельная переменная-флаг здесь не сработает. Частая ошибка в конфигах — завести $composite_enabled, аккуратно выставлять его в ноль по всем условиям, а в try_files подставлять $composite_file, который при этом остаётся заполненным. Флаг нигде не проверяется, статика отдаётся всем подряд — включая авторизованных пользователей. Конфиг при этом валиден, nginx стартует, и ошибка обнаруживается только когда кто-то видит чужие данные. Обнуляйте именно ту переменную, которую читает try_files.

Шаг 2: Настройка динамических областей

Здесь находится вся суть работы с композитом. Правило одно, и оно не имеет исключений: всё, что отличается у разных пользователей, обязано быть завёрнуто во Frame. Не завёрнутое попадёт в общий статический HTML в том виде, в каком его увидел первый посетитель, и будет показано всем остальным.

Список того, что забывают чаще всего: имя пользователя в шапке, счётчик корзины, счётчик сравнения, «избранное», недавно просмотренные товары, региональность (город в шапке, цены и наличие по региону), CSRF-токены в формах и персональные цены.

php
<?php
// В шаблоне компонента или header.php
// Начало динамической области
$frame = new \Bitrix\Main\Composite\Engine\Frame(
    'header-user-area', // уникальный ID
    false // не использовать отложенную загрузку
);
$frame->begin();
?>
<div class="user-panel">
    <?php if ($USER->IsAuthorized()): ?>
        Привет, <?= $USER->GetFullName() ?>!
        <a href="/personal/">Личный кабинет</a>
    <?php else: ?>
        <a href="/auth/">Войти</a>
    <?php endif; ?>
</div>
<?php
$frame->end();

Динамическая корзина

php
<?php
// Корзина — типичный пример динамической области
$frame = new \Bitrix\Main\Composite\Engine\Frame('basket-mini');
$frame->begin();
$basket = \Bitrix\Sale\Basket::loadItemsForFUser(
    \Bitrix\Sale\Fuser::getId(),
    \Bitrix\Main\Context::getCurrent()->getSite()
);
$count = $basket->count();
$sum = $basket->getPrice();
?>
<a href="/cart/" class="basket-link">
    <span class="basket-count"><?= $count ?></span>
    <span class="basket-sum"><?= CurrencyFormat($sum, 'RUB') ?></span>
</a>
<?php
$frame->end();
⚠️ Важно

CSRF-токен в статическом HTML. bitrix_sessid() привязан к сессии, а композит отдаёт всем одну и ту же страницу. Форма с зашитым в HTML токеном первого посетителя у остальных перестанет проходить проверку — и вы получите загадочные жалобы «форма не отправляется», воспроизводящиеся только у части пользователей. Формы либо целиком в динамической области, либо токен подставляется скриптом после загрузки.

Шаг 3: Исключения из кэширования

Некоторые страницы не должны кэшироваться:

php
<?php
// /bitrix/php_interface/init.php
use Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler(
    'main',
    'OnBeforeEndBufferContent',
    function () {
        $uri = $_SERVER['REQUEST_URI'];
        // Исключаем из композита
        $excludePatterns = [
            '/personal/',      // Личный кабинет
            '/cart/',          // Корзина
            '/order/',         // Оформление заказа
            '/auth/',          // Авторизация
            '/search/',        // Поиск (уникальные запросы)
            '/ajax/',          // AJAX-эндпоинты
        ];
        foreach ($excludePatterns as $pattern) {
            if (strpos($uri, $pattern) === 0) {
                \Bitrix\Main\Composite\Page::getInstance()->setEnabled(false);
                break;
            }
        }
    }
);
💡 Совет

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

Шаг 4: Инвалидация кэша

Когда контент меняется, нужно сбросить кэш:

php
<?php
// Сброс конкретной страницы
\Bitrix\Main\Composite\Page::deleteByPath('/catalog/product/iphone-15/');
// Сброс всего каталога
\Bitrix\Main\Composite\Page::deleteByPath('/catalog/', true);
// Полный сброс композита
\Bitrix\Main\Composite\Page::deleteAll();

Автоматический сброс при изменении товара

php
<?php
// init.php
EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnAfterIBlockElementUpdate',
    function ($arFields) {
        // Сбрасываем кэш страницы товара
        $element = \CIBlockElement::GetByID($arFields['ID'])->GetNext();
        if ($element && $element['DETAIL_PAGE_URL']) {
            \Bitrix\Main\Composite\Page::deleteByPath($element['DETAIL_PAGE_URL']);
        }
        // Сбрасываем страницу раздела
        if ($element['IBLOCK_SECTION_ID']) {
            $section = \CIBlockSection::GetByID($element['IBLOCK_SECTION_ID'])->GetNext();
            if ($section && $section['SECTION_PAGE_URL']) {
                \Bitrix\Main\Composite\Page::deleteByPath($section['SECTION_PAGE_URL']);
            }
        }
    }
);
💡 Совет

Проверьте имена методов в своей версии Битрикса. За хранение статических страниц композита отвечает \Bitrix\Main\Data\StaticHtmlCache — у него есть deleteEntry($path) для одной страницы и deleteAll() для полного сброса. Набор публичных методов у классов композита между версиями менялся, поэтому перед тем, как ставить вызов в обработчик события, убедитесь, что он существует: молча не сработавшая инвалидация — это устаревшие цены на витрине, о которых вы узнаете последним.

Отладка проблем

Обязательный тест перед выкладкой

Прежде чем включать композит на боевом сайте, пройдите этот сценарий — он занимает пять минут и ловит почти все утечки персональных данных:

bash
# 1. Страница глазами анонима — эталон
curl -s https://site.ru/catalog/ > anon.html

# 2. Та же страница с куками авторизованного пользователя
curl -s -b "BITRIX_SM_LOGIN=ivanov; BITRIX_SM_UIDH=..." https://site.ru/catalog/ > auth.html

# 3. Разница должна быть только в динамических областях
diff anon.html auth.html

Затем — вручную: авторизуйтесь, положите товар в корзину, откройте сайт в приватном окне и проверьте, что счётчик корзины пуст, а в шапке нет вашего имени. Если есть — ищите незавёрнутую область.

Страница не кэшируется

Проверьте заголовки ответа:

bash
curl -I https://site.ru/catalog/product/iphone/
# Ищите: X-Bitrix-Composite: Cache (HIT) или Cache (MISS)

Причины MISS:

  • Ошибки PHP на странице
  • Динамические компоненты без frame
  • Страница в списке исключений
  • Превышен лимит размера страницы

Проверка из админки

Настройки → Автокеширование → Статистика композитного сайта.

Здесь видно:

  • Процент попаданий в кэш
  • Причины пропусков
  • Размер кэша
⚠️ Важно

Частая ошибка: Забыли исключить корзину или личный кабинет. Пользователи видят чужие данные! Всегда тестируйте под разными учётками.

Результаты

Типичные показатели после включения:

МетрикаДоПосле
TTFB250–400ms10–30ms
PageSpeed50–7085–95
Нагрузка на CPU30–50%5–10%
Запросов/сек50–100500–2000

Итоги

Композит даёт лучшее соотношение «эффект к трудозатратам» среди всего, что можно сделать с производительностью Битрикса. Но включать его стоит только когда выполнены четыре условия.

Все персональные блоки завёрнуты во Frame — и это проверено тестом с двумя наборами кук, а не «вроде всё вынесли».

Персональные разделы исключены из композита, и список исключений кто-то поддерживает при добавлении новых разделов.

Инвалидация настроена и проверена. Не «вызов добавлен в обработчик», а «поменял цену, обновил страницу, увидел новую цену».

Есть план отката. Композит выключается одной галочкой — знайте, где она, до того, как она понадобится.

И одно наблюдение из практики: если для работы композита приходится оборачивать во Frame половину страницы, вы боретесь не с той проблемой. Динамическая область — это тоже запрос к PHP, просто отложенный. Страница, состоящая из динамических областей, получает всю прежнюю нагрузку плюс накладные расходы на дополнительные AJAX-запросы. В таком случае разумнее заняться обычным кэшированием компонентов и запросами к базе.

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

Что такое композитный сайт в Битрикс?

Композит — это технология агрессивного кэширования в 1С-Битрикс: готовый HTML сохраняется в файл в /bitrix/html_pages/ и отдаётся следующим посетителям напрямую из nginx, без обращения к PHP и MySQL. Динамические блоки при этом подгружаются отдельными AJAX-запросами.

Насколько композит ускоряет сайт на Битрикс?

По типичным замерам TTFB снижается с 250–400 мс до 10–30 мс, оценка PageSpeed растёт с 50–70 до 85–95, нагрузка на CPU падает с 30–50% до 5–10%, а сайт держит 500–2000 запросов в секунду вместо 50–100.

Почему после включения композита пользователи видят чужие данные?

Потому что персональный блок не завёрнут во Frame и попал в общий статический HTML в том виде, в каком его увидел первый посетитель. Чаще всего забывают имя пользователя в шапке, счётчики корзины и сравнения, региональные цены и CSRF-токены в формах.

Какие страницы нельзя кэшировать композитом?

Корзину, оформление заказа, личный кабинет и любые полностью персонализированные разделы. Их исключают из композита списком, и этот список нужно поддерживать при добавлении новых разделов.

Как сбросить кэш композита при изменении товара?

Через обработчик OnAfterIBlockElementUpdate, который удаляет закэшированную страницу по её DETAIL_PAGE_URL. За хранение статических страниц отвечает \Bitrix\Main\Data\StaticHtmlCache с методами deleteEntry($path) и deleteAll(); набор публичных методов между версиями Битрикса менялся, поэтому вызов стоит проверить в своей версии — молча не сработавшая инвалидация означает устаревшие цены на витрине.

Когда композит не стоит включать?

Если ради него приходится оборачивать во Frame половину страницы. Динамическая область — это тот же запрос к PHP, только отложенный, поэтому такая страница получает прежнюю нагрузку плюс накладные расходы на AJAX. В этом случае эффективнее заняться кэшированием компонентов и запросами к базе.