Шаблон сайта выводит H1 раньше, чем компонент получает название товара. Хлебные крошки находятся над содержимым, а последний пункт цепочки становится известен внутри компонента. В обоих случаях место вывода уже пройдено, но данные ещё можно подставить — через отложенные функции Битрикс.

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

Вывести поздний заголовок: ShowTitle() вместо GetTitle()

Если H1 расположен в header.php шаблона сайта, обычное получение заголовка может сработать слишком рано:

<h1><?= $APPLICATION->GetTitle() ?></h1>

GetTitle() возвращает текущее значение. Последующий SetTitle() не изменит уже выведенную строку. Замените этот фрагмент отложенным выводом:

<h1><?php $APPLICATION->ShowTitle(false); ?></h1>

В коде страницы после подключения /bitrix/header.php задайте заголовок:

$APPLICATION->SetTitle('Доставка');

Когда Битрикс соберёт буфер, H1 получит «Доставка». ShowTitle() регистрирует получение значения через AddBufferContent(): функция читает заголовок позднее, а её результат вставляется на место вызова в шаблоне.

Это работа в пределах текущего серверного запроса. Отложенная функция не создаёт фоновую задачу и не запускает JavaScript. Сам ShowTitle() не нужно оборачивать в echo или ещё один AddBufferContent().

Задать разные H1 и title, вывести description

Название в тексте страницы и заголовок вкладки браузера часто различаются. Например, H1 должен быть коротким, а <title> — уточнять тему. В коде страницы после подключения шапки задайте оба значения и описание:

$APPLICATION->SetTitle('Доставка');
$APPLICATION->SetPageProperty('title', 'Условия доставки заказов');
$APPLICATION->SetPageProperty('description', 'Сроки и способы получения заказа.');

Внутри <head> файла header.php шаблона сайта:

<title><?php $APPLICATION->ShowTitle(); ?></title>
<?php $APPLICATION->ShowHead(); ?>

Для H1 внутри <body>:

<h1><?php $APPLICATION->ShowTitle(false); ?></h1>

ShowTitle() без параметра сначала ищет свойство title, затем использует значение из SetTitle(). Параметр false отключает поиск свойства: H1 получит «Доставка», а <title> — «Условия доставки заказов».

ShowHead() уже выводит стандартные метатеги и ресурсы страницы. Не добавляйте рядом отдельный ShowMeta('description'), иначе описание может продублироваться. ShowMeta() нужен при самостоятельной сборке метатегов, а ShowProperty() выводит значение свойства без разметки метатега. Он не экранирует произвольный HTML автоматически.

Для ресурсов также существуют ShowHeadStrings(), ShowHeadScripts() и ShowCSS(); обычно достаточно штатного ShowHead(). ShowTitle() удаляет теги, но не является универсальным экранированием для любого HTML-контекста. Пользовательский текст внутри собственного HTML-фрагмента нужно экранировать при формировании этого фрагмента.

Добавить пункт хлебных крошек ниже места их вывода

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

В header.php шаблона, внутри <body>, подключите цепочку:

$APPLICATION->IncludeComponent('bitrix:breadcrumb', '', [
    'START_FROM' => '0',
    'PATH' => '',
    'SITE_ID' => SITE_ID,
]);

В коде страницы после подключения шапки добавьте пункт:

$APPLICATION->AddChainItem('Доставка');

Компонент регистрирует вызов GetNavChain() через AddBufferContent(). Цепочка собирается после того, как страница добавила свои пункты. Если компонент контента уже добавляет этот пункт сам, повторный AddChainItem() не нужен: отложенный вывод не удаляет дубли.

Для цепочки есть и штатный ShowNavChain(). Другой пример встроенного отложенного вывода — ShowPanel(), который размещает административную панель на публичной странице. Писать собственный callback для этих задач обычно не требуется.

Вывести готовый HTML выше по странице: ShowViewContent() и AddViewContent()

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

$APPLICATION->ShowViewContent('catalog_notice');

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

$APPLICATION->AddViewContent(
    'catalog_notice',
    '<p>Наличие товара уточняется при подтверждении заказа.</p>',
    100
);

ShowViewContent() откладывает чтение области, а AddViewContent() добавляет готовую строку. Третий аргумент задаёт позицию: фрагмент с позицией 100 появится раньше фрагмента с позицией 200, независимо от порядка добавления.

Несколько добавлений объединяются. Два вызова ShowViewContent() покажут область в двух местах, а пустая область ничего не выведет. При необходимости очистить её используйте $APPLICATION->clearViewContent('catalog_notice'). Имя должно отличаться от имён областей других компонентов.

Так удобно добавлять HTML из кода страницы или эпилога компонента. Для кешируемого template.php нужен следующий приём: обычный AddViewContent() не сохраняет область в данных кеша шаблона.

Вывести анонс news.detail над компонентом и сохранить его в кеше

Допустим, bitrix:news.detail выводит подробный текст новости, а её анонс должен находиться выше — в шаблоне сайта. Если добавить область обычным AddViewContent() из template.php, она может исчезнуть при попадании в кеш. Пара SetViewTarget() / EndViewTarget() сохраняет захваченный HTML вместе с данными компонента.

В header.php шаблона сайта, внутри <body> перед содержимым страницы, задайте место анонса:

$APPLICATION->ShowViewContent('news_announcement');

В template.php компонента news.detail:

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) { die(); }
/** @var CBitrixComponentTemplate $this */
$announcement = (string)($arResult['~PREVIEW_TEXT'] ?? '');
?>
<?php if ($announcement !== ''): ?>
    <?php $this->SetViewTarget('news_announcement', 100); ?>
    <aside class="news-announcement">
        <?= htmlspecialchars(strip_tags($announcement), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>
    </aside>
    <?php $this->EndViewTarget(); ?>
<?php endif; ?>

news.detail уже получает анонс элемента. ~PREVIEW_TEXT содержит исходное значение до HTML-экранирования; пример удаляет разметку и выводит анонс обычным текстом. Если шаблон уже показывает этот анонс рядом с подробным текстом, уберите прежний вывод, чтобы не получить повтор.

Здесь $this — объект шаблона компонента. SetViewTarget() начинает захват, а EndViewTarget() передаёт HTML в область и регистрирует его у компонента. При попадании в кеш Битрикс восстановит область без повторного выполнения template.php. SetResultCacheKeys() для анонса не нужен: сохраняется уже сформированный HTML. Если анонс не заполнен, пример не создаёт пустой <aside>.

Закрывайте захват явно. Начало другой области завершает предыдущую, поэтому эти вызовы не стоит использовать как вложенные контейнеры. Внутри захвата формируйте обычный HTML, без новых отложенных функций и ручного завершения буферов.

Передать данные из шаблона news.detail в component_epilog.php

Эпилог нужен для действий, которые должны повторяться и при попадании в кеш компонента. Например, над новостью нужно вывести подпись «Материал: название новости» через отдельное свойство страницы. Подготовим значение в result_modifier.php шаблона bitrix:news.detail, сохраним его в результате и установим свойство в component_epilog.php.

В result_modifier.php рядом с template.php добавьте:

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) { die(); }
/** @var CBitrixComponentTemplate $this */

$name = trim((string)($arResult['~NAME'] ?? ''));
$arResult['ARTICLE_LABEL'] = $name !== '' ? 'Материал: ' . $name : '';
$this->getComponent()->SetResultCacheKeys(['ARTICLE_LABEL']);

result_modifier.php получает результат штатного компонента до вывода шаблона. Через getComponent() он обращается к компоненту и добавляет ARTICLE_LABEL к сохраняемым ключам. Переписывать component.php или добавлять свой StartResultCache() не нужно.

В component_epilog.php:

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) { die(); }

$APPLICATION->SetPageProperty(
    'article_label',
    htmlspecialchars((string)($arResult['ARTICLE_LABEL'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
);

В header.php шаблона сайта, внутри <body>, разместите вывод свойства:

$APPLICATION->ShowProperty('article_label');

ShowProperty() прочитает значение при сборке страницы. Он сам не экранирует HTML, поэтому эпилог сохраняет в свойство уже экранированный текст. Например, для новости «Открытие магазина» на месте вызова появится «Материал: Открытие магазина».

При первом построении кеша выполняется result_modifier.php, а ARTICLE_LABEL сохраняется в $arResult. На повторном запросе Битрикс берёт сохранённое значение и снова вызывает эпилог. После добавления нового ключа очистите кеш компонента: прежняя запись его не содержит.

Для стандартных H1 и метатегов сначала используйте параметры самого news.detail: компонент умеет устанавливать их и после получения результата из кеша. Этот пример использует отдельное свойство article_label, чтобы не конкурировать со штатной установкой заголовков.

component_epilog.php — эпилог конкретного шаблона компонента, а не событие OnEpilog всей страницы. Здесь доступны $arResult, $arParams и $component, но $this уже не объект CBitrixComponentTemplate: переносить сюда $this->SetViewTarget() нельзя. Если из эпилога нужен готовый HTML в области, используйте $APPLICATION->AddViewContent().

Вычислить свой HTML при сборке страницы: AddBufferContent()

Именованная область хранит готовые строки. Собственный callback нужен, если значение надо прочитать именно при сборке буфера. Например, шаблон заранее оставляет место для плашки, а текст плашки задаётся позднее через свойство страницы.

В /local/php_interface/init.php определите callback:

<?php
function projectPageBadge(string $property): string
{
    $text = (string)$GLOBALS['APPLICATION']->GetPageProperty($property, '');
    if ($text === '') { return ''; }
    return '<span class="page-badge">'
        . htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
        . '</span>';
}

В header.php шаблона, внутри <body>, зарегистрируйте вывод:

$APPLICATION->AddBufferContent('projectPageBadge', 'page_badge');

В коде страницы после подключения шапки задайте значение:

$APPLICATION->SetPageProperty('page_badge', 'Информация для покупателя');

На месте регистрации появится <span> с заданным текстом. Callback получает имя свойства и читает его значение позднее. Он должен вернуть строку: echo внутри функции не заменяет return.

Не передавайте позднее значение уже вычисленным аргументом:

// Этот вызов запомнит текущее значение свойства.
$APPLICATION->AddBufferContent(
    'htmlspecialchars',
    (string)$APPLICATION->GetPageProperty('page_badge', ''),
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);

PHP вычисляет аргументы при регистрации. Если свойство ещё пустое, callback получит пустую строку даже после её изменения. Передача ключа page_badge позволяет прочитать актуальные данные внутри функции.

Именованная функция также позволяет избежать документированного ограничения анонимных callbacks при AJAX_MODE = Y у компонентов. Сам режим AJAX всё равно требует отдельной проверки. На этапе сборки HTML лучше читать уже подготовленные данные, а не выполнять сетевые запросы или повторные выборки из базы.

Выбрать событие по этапу: OnEpilog и завершение буфера

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

Порядок внутри завершения буфера:

  1. OnBeforeEndBufferContent — перед вычислением отложенных участков; без HTML-параметра.
  2. Вызовы зарегистрированных отложенных функций.
  3. Объединение их результатов с остальными частями страницы.
  4. OnEndBufferContent — обработчики получают собранную строку HTML по ссылке.

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

Заменить фрагмент готового HTML: OnEndBufferContent

Обработку буфера используйте, когда нет подходящей точки для изменения исходного вывода. Например, собственный шаблон оставляет маркер, который нужно заменить при сборке страницы. В /local/php_interface/init.php добавьте обработчик:

<?php
AddEventHandler('main', 'OnEndBufferContent', 'projectReplacePageMarker');
function projectReplacePageMarker(&$html): void
{
    global $APPLICATION;
    if ($APPLICATION->GetCurPage() !== '/delivery/index.php'
        || ($_SERVER['REQUEST_METHOD'] ?? '') !== 'GET'
        || stripos($html, '</html>') === false) {
        return;
    }
    $html = str_replace('<!--PAGE_NOTICE-->',
        '<p>Наличие товара уточняется при подтверждении заказа.</p>', $html);
}

В /delivery/index.php, между подключениями /bitrix/header.php и /bitrix/footer.php, выведите маркер:

echo '<!--PAGE_NOTICE-->';

На его месте появится абзац. Условие ограничивает обработчик выбранной страницей и полным HTML-ответом на GET-запрос. Если путь другой, замените его в проверке. При отсутствии маркера строка не изменится.

Композитная обработка может вызвать OnEndBufferContent повторно для другой версии буфера. Замена маркера переносит повторный вызов безопасно: она не дописывает второй абзац к уже обработанной строке. Не используйте такое событие для отправки уведомлений или увеличения счётчиков, которые должны срабатывать ровно один раз.

Найти причину ошибки: позднее значение, кеш или сброшенный буфер

Если заголовок остаётся старым, проверьте ранний GetTitle() и последующие вызовы SetTitle(). Для различающихся H1 и <title> проверьте параметр ShowTitle().

Если область появляется только после очистки кеша, найдите AddViewContent() в кешируемом шаблоне. Для захвата HTML используйте SetViewTarget(), а установку свойств страницы перенесите в component_epilog.php. Когда в эпилоге отсутствует поле, проверьте SetResultCacheKeys() и очистите старую запись кеша.

Если блок повторяется, проверьте количество вызовов ShowViewContent() и добавлений в область. AddViewContent() дополняет содержимое, а не заменяет предыдущую строку.

Если callback выполняется сразу, проверьте жизненный цикл страницы. Без BX_BUFFER_USED === true AddBufferContent() немедленно вызывает функцию и выводит результат. Не определяйте эту константу вручную: нужны штатные пролог, эпилог и управление буфером.

Если обычная страница работает, а AJAX-ответ — нет, проверьте наличие полного цикла страницы и вызовы RestartBuffer(). Этот метод сбрасывает накопленные части буфера и регистрации отложенного вывода; он не запускает их повторно. Лишние ob_end_clean(), ob_end_flush() и незакрытый SetViewTarget() также нарушают ожидаемую последовательность.

Кеш компонента и композит — разные уровни. SetViewTarget() сохраняет HTML области вместе с компонентом, но не делает её персональной. Имя пользователя, корзина и персональная цена требуют соответствующего разделения кеша, а при композите — динамических областей. Сам AddBufferContent() не гарантирует выполнение PHP при выдаче сохранённой композитной страницы.

После подключения выбранного приёма сравните исходный HTML первого ответа после очистки кеша и повторного ответа без очистки. Нужный заголовок или блок должен присутствовать в обоих. Для своего HTML проверьте текст с кавычками, <, > и &: символы должны оставаться текстом. AJAX и композит проверяйте отдельно, если проект их использует.