В шаблонах и компонентах Битрикс до сих пор часто пишут $APPLICATION->SetAdditionalCSS / AddHeadScript «где придётся»: в header.php, в result_modifier, в ajax-ответе. Потом стили дублируются, скрипт уезжает в середину HTML, а на AJAX-перерисовке компонент теряет CSS. Нормальный контракт ядра — Bitrix\Main\Page\Asset.
Рядом по теме: проверка уже подключённого CSS, AJAX в компонентах, Application / Context, Loader / ModuleManager. Ниже — addCss/addJs/addString, порядок вывода, уникальность путей и типичные ошибки на проде.
Зачем Asset, а не SetAdditionalCSS напрямую
$APPLICATION по-прежнему работает и внутри вызывает тот же менеджер ресурсов. Но прямой доступ к Asset::getInstance() удобнее в модулях, сервисах и коде без глобального $APPLICATION:
- единая точка регистрации CSS/JS/строк в
<head>и перед</body>; - дедупликация по пути файла — повторный
addCssтого же URL не плодит второй<link>; - явный режим вывода (шапка / тело / отложенно), без «вставил echo в template.php и получил FOUC»;
- проще тестировать и вызывать из D7-сервисов после ServiceLocator.
Правило простое: новый код модуля и local/ — через Bitrix\Main\Page\Asset. Старые вызовы $APPLICATION->SetAdditionalCSS оставляйте, пока не трогаете файл; при правке — переводите на Asset.
Базовое API: addCss, addJs, addString
<?php
use Bitrix\Main\Page\Asset;
$asset = Asset::getInstance();
// CSS относительно корня сайта
$asset->addCss('/local/css/catalog.filters.css');
// JS
$asset->addJs('/local/js/catalog.filters.js');
// Произвольная строка в head (meta, inline-критика, preconnect)
$asset->addString(
'<link rel="preconnect" href="https://cdn.example.com" crossorigin>',
true // unique: не дублировать одинаковую строку
);
Пути лучше начинать с / от document root. Относительные «как в шаблоне» ломаются на ЧПУ и во вложенных разделах. Для файлов модуля удобно собирать URL через Loader / путь модуля, а не хардкодить /bitrix/modules/... в публичку.
Где вызывать: компонент, init.php, epilog
Типичные места регистрации:
- В компоненте (
component.phpилиclass.php) — если ресурс нужен только этому компоненту. Регистрируйте до вывода шаблона, не внутри цикла по элементам. - В
template.php— допустимо для уникальных стилей шаблона; избегайте вызова на каждый элемент списка. - В
local/php_interface/init.phpили обработчикеOnEpilog/OnProlog— для глобальных библиотек сайта. - В ajax-ветке — если ответ HTML-фрагмент, CSS/JS всё равно нужно зарегистрировать так, чтобы ядро/BX подтянули их в ответ (или отдать критичные стили инлайном осознанно).
<?php
// local/components/vendor/widget/class.php
use Bitrix\Main\Loader;
use Bitrix\Main\Page\Asset;
class WidgetComponent extends CBitrixComponent
{
public function executeComponent()
{
if ($this->startResultCache()) {
// ... данные ...
$this->includeComponentTemplate();
}
// После кеша шаблона ресурсы всё равно нужны на каждом хите
$this->registerAssets();
}
protected function registerAssets(): void
{
$asset = Asset::getInstance();
$asset->addCss($this->getPath() . '/templates/.default/style.css');
$asset->addJs($this->getPath() . '/templates/.default/script.js');
}
}
Важно: если регистрацию спрятать только внутрь startResultCache() без повторного вызова снаружи, при отдаче из кеша компонента CSS/JS могут не попасть на страницу. Либо регистрируйте ресурсы вне кеша, либо используйте $this->SetResultCacheKeys / стандартные style.css/script.js шаблона компонента (ядро подключает их само).
Дедупликация и проверка «уже подключено»
addCss/addJs сами не плодят второй тег для того же пути. Отдельная ручная проверка нужна реже, чем кажется. Если всё же надо узнать список (отладка, условная логика, миграция со старого кода) — смотрите массив через $APPLICATION->GetCssList() / исторический GetCSSArray() и разбор в статье «Проверить, подключён ли стиль».
<?php
use Bitrix\Main\Page\Asset;
use Bitrix\Main\Page\AssetLocation;
$asset = Asset::getInstance();
$path = '/local/css/shared.ui.css';
// Достаточно одного вызова из разных компонентов:
$asset->addCss($path);
$asset->addCss($path); // второй раз — без второго <link>
// Строки дедуплицируйте флагом unique
$asset->addString('<meta name="viewport" content="width=device-width, initial-scale=1">', true);
Грабля: один и тот же файл под разными URL (/local/css/a.css и /local/css/a.css?v=2, или с доменом и без) считается разным ресурсом. Нормализуйте путь и версию централизованно (query ?v= из конфига релиза).
Порядок и место вывода
По умолчанию CSS уходит в head, JS — в зависимости от настроек сайта/композита и режима Asset. Для строк можно указать локацию:
<?php
use Bitrix\Main\Page\Asset;
use Bitrix\Main\Page\AssetLocation;
$asset = Asset::getInstance();
// В <head>
$asset->addString(
'<style>.hero{min-height:40vh}</style>',
true,
AssetLocation::BEFORE_CSS
);
// Перед закрытием body (если поддерживается режимом)
$asset->addString(
'<script>window.__CATALOG_BOOTSTRAP__=1</script>',
true,
AssetLocation::AFTER_JS
);
Не вставляйте большие бандлы инлайном через addString — это убивает кеш браузера. Инлайн оставляйте для критичного CSS «above the fold» или крошечных конфигов (window.__CONFIG__). Тяжёлые библиотеки — отдельными файлами с долгим Cache-Control.
Связка с шаблоном компонента и Extension
Если в каталоге шаблона лежат style.css и script.js, ядро подключит их автоматически при показе компонента — отдельный addCss не обязателен. Asset нужен для:
- общих библиотек из
/local/css, CDN, npm-сборки; - условного подключения (только на детальной, только для авторизованных);
- ресурсов, которые регистрирует сервис/модуль вне шаблона.
Для JS-расширений ядра (ui.vue, main.core, кастомных extension.php) предпочтителен Bitrix\Main\UI\Extension::load('vendor.widget'): зависимости, порядок и локализация подтянутся сами. Asset при этом остаётся правильным слоем для «простых» файлов сайта.
<?php
use Bitrix\Main\UI\Extension;
use Bitrix\Main\Page\Asset;
// Ядровое/модульное расширение со своими deps
Extension::load(['ui.buttons', 'vendor.widget']);
// Плоский файл витрины
Asset::getInstance()->addJs('/local/js/landing.hero.js');
AJAX и композит
При компонентном AJAX (AJAX_MODE=Y или ручной HTML-фрагмент) браузер уже загрузил страницу. Новые CSS/JS из фрагмента ядро старается догрузить через BX, если ресурсы зарегистрированы штатно. Если вы отдаёте «голый» JSON и сами рисуете DOM — CSS нужно либо подключить заранее на полной странице, либо грузить скриптом явно.
Практичный подход: все стили виджета регистрируйте на полной отрисовке листинга; в ajax-ответе меняйте только разметку. Подробнее про ajaxMode и JSON-ответы — в AJAX в компонентах и Web\Json.
С композитом не кешируйте HTML, в который через addString попали персональные данные или одноразовые токены. Статические CSS/JS с постоянным URL — безопасны; инлайн-конфиг пользователя — нет.
Типичные ошибки
- Регистрация внутри кеша компонента без повтора снаружи. После хита из кеша страница «лысая».
- Относительные пути (
css/style.cssбез ведущего/) — ломаются на вложенных URL. - Echo <script> в template.php вместо Asset — порядок, CSP, дедуп и композит страдают.
- Два URL на один файл (с query и без, http/https, с доменом) — двойная загрузка.
- Подключение тяжёлого бандла на всех страницах из init.php — PageSpeed падает на корзине и 404.
- addString с огромным CSS — нет кеша файла в браузере.
- Забыли charset/минификацию на CDN и подключили «dev»-сборку на прод.
- Смешение Extension::load и ручного addJs той же библиотеки — двойной Vue/ядро, конфликты глобалов.
Мини-чеклист внедрения
- В новом коде — только
Asset::getInstance()(или авто-файлы шаблона / Extension). - Пути абсолютные от корня сайта, версия — один query из релиза.
- Ресурсы компонента регистрируйте так, чтобы они попадали и при отдаче из кеша.
- Глобальные библиотеки — по условию раздела/шаблона, не «на весь сайт всегда».
- Для модульного JS с зависимостями —
Extension::load, не копипаста в/local/js. - В ajax-сценариях заранее грузите CSS на полной странице; JSON — через
Web\Json.
Быстрая отладка
<?php
use Bitrix\Main\Page\Asset;
// Временно на стейдже: посмотреть, что накопил Asset к концу хита
AddMessage2Log([
'css' => Asset::getInstance()->getCss(),
'js' => Asset::getInstance()->getJs(),
], 'local.asset');
// В браузере: DevTools → Network → фильтр CSS/JS,
// поиск дублей одного файла с разными query.
На проде такой лог отключайте: строка с полным списком ресурсов на каждый хит быстро раздует журнал.
Вывод
Bitrix\Main\Page\Asset — правильный слой для CSS/JS в модулях и local/: дедуп путей, предсказуемое место в HTML, работа без глобального $APPLICATION. Старые SetAdditionalCSS/AddHeadScript не запрещены, но для новой разработки лучше сразу Asset + авто-файлы шаблона + Extension::load для ядровых пакетов. Проверку «уже есть в списке» оставляйте для отладки и легаси; для обычного подключения хватает повторного addCss с тем же путём. Дальше по стеку фронта компонента — AJAX без костылей, сериализация ответов — Json, HTTP-контекст — Application/Context.
