Композит — самая эффектная оптимизация из доступных в Битриксе: TTFB падает с трёхсот миллисекунд до двадцати, и на графиках это выглядит как обрыв. Она же — самая опасная, потому что механизм по своей сути отдаёт один и тот же HTML всем посетителям. Одна забытая динамическая область — и пользователь видит в шапке чужое имя, а в корзине чужие товары.
Поэтому статья про композит — это не «как включить», а «что проверить перед тем, как включать».
Что такое композитный сайт
Композит — это технология агрессивного кэширования в Битрикс. Вместо генерации страницы на каждый запрос, сервер отдаёт готовый HTML-файл напрямую из nginx, минуя PHP.
Результат: время ответа падает с 200–500ms до 5–20ms.
Когда использовать: каталоги, карточки товаров, статьи, лендинги — любые страницы с редко меняющимся контентом. Не подходит для корзины, личного кабинета и других персонализированных страниц.
Как это работает
- Первый посетитель открывает страницу — PHP генерирует HTML
- Битрикс сохраняет HTML в файл
/bitrix/html_pages/... - Следующие посетители получают файл напрямую от nginx
- Динамические блоки (корзина, авторизация) подгружаются через AJAX
Без композита:
Браузер → nginx → PHP → MySQL → PHP → nginx → Браузер
[=================== 300ms ===================]
С композитом:
Браузер → nginx → HTML-файл → Браузер
[======= 15ms =======]Шаг 1: Включение композита
Админка → Настройки → Настройки продукта → Автокеширование → Композитный сайт.
<?php
// Или через код в init.php
\Bitrix\Main\Composite\Page::getInstance()->setEnabled(true);Настройки nginx
Отдачу статики настраивает nginx. Логика простая: собрать путь к возможному файлу композита и сбросить его в пустую строку в каждом случае, когда отдавать статику нельзя. try_files с пустой переменной просто уходит в фолбэк на PHP.
# /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
// В шаблоне компонента или 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
// Корзина — типичный пример динамической области
$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
// /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
// Сброс конкретной страницы
\Bitrix\Main\Composite\Page::deleteByPath('/catalog/product/iphone-15/');
// Сброс всего каталога
\Bitrix\Main\Composite\Page::deleteByPath('/catalog/', true);
// Полный сброс композита
\Bitrix\Main\Composite\Page::deleteAll();Автоматический сброс при изменении товара
<?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() для полного сброса. Набор публичных методов у классов композита между версиями менялся, поэтому перед тем, как ставить вызов в обработчик события, убедитесь, что он существует: молча не сработавшая инвалидация — это устаревшие цены на витрине, о которых вы узнаете последним.
Отладка проблем
Обязательный тест перед выкладкой
Прежде чем включать композит на боевом сайте, пройдите этот сценарий — он занимает пять минут и ловит почти все утечки персональных данных:
# 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Затем — вручную: авторизуйтесь, положите товар в корзину, откройте сайт в приватном окне и проверьте, что счётчик корзины пуст, а в шапке нет вашего имени. Если есть — ищите незавёрнутую область.
Страница не кэшируется
Проверьте заголовки ответа:
curl -I https://site.ru/catalog/product/iphone/
# Ищите: X-Bitrix-Composite: Cache (HIT) или Cache (MISS)Причины MISS:
- Ошибки PHP на странице
- Динамические компоненты без frame
- Страница в списке исключений
- Превышен лимит размера страницы
Проверка из админки
Настройки → Автокеширование → Статистика композитного сайта.
Здесь видно:
- Процент попаданий в кэш
- Причины пропусков
- Размер кэша
Частая ошибка: Забыли исключить корзину или личный кабинет. Пользователи видят чужие данные! Всегда тестируйте под разными учётками.
Результаты
Типичные показатели после включения:
| Метрика | До | После |
|---|---|---|
| TTFB | 250–400ms | 10–30ms |
| PageSpeed | 50–70 | 85–95 |
| Нагрузка на CPU | 30–50% | 5–10% |
| Запросов/сек | 50–100 | 500–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. В этом случае эффективнее заняться кэшированием компонентов и запросами к базе.