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

URL: https://dmeshcheryakov.ru/blog/bitrix-composite/
Раздел: 1С-Битрикс
Теги: 1С-Битрикс, Performance, Кэширование
Опубликовано: 2026-09-07
Обновлено: 2026-08-29
Автор: Дмитрий Мещеряков (https://dmeshcheryakov.ru)

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

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

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

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

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

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

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

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

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

```
Без композита:
Браузер → 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
- Страница в списке исключений
- Превышен лимит размера страницы

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

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

Здесь видно:
- Процент попаданий в кэш
- Причины пропусков
- Размер кэша

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

## Результаты

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

| Метрика | До | После |
|---------|-----|-------|
| TTFB | 250–400ms | 10–30ms |
| PageSpeed | 50–70 | 85–95 |
| Нагрузка на CPU | 30–50% | 5–10% |
| Запросов/сек | 50–100 | 500–2000 |

## Итоги

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

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

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

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

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

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