Async JS Loading Configuration for 1C-Bitrix
The most common symptom: Lighthouse reports "Eliminate render-blocking resources", and the list includes jQuery, Swiper, and Bitrix component scripts. The browser parses HTML, encounters a <script src="..."> without attributes, and stops rendering until the file is downloaded and executed. A typical Bitrix site accumulates 5–10 such blocking scripts.
How Bitrix Manages JS
Bitrix registers scripts via CMain::AddHeadScript() and Asset::getInstance()->addJs(). The ShowHead() method outputs them in <head> without defer/async. Components add scripts via $APPLICATION->AddHeadScript() — the same behavior. The only way to override this is at the template level or by post-processing the output buffer.
Adding defer/async via the Template
The safest approach is to use the OnEndBufferContent event for post-processing the final HTML:
// local/php_interface/init.php
AddEventHandler('main', 'OnEndBufferContent', 'deferScripts');
function deferScripts(string &$content): void
{
// Add defer to all external scripts except explicitly excluded ones
$exclude = ['jquery.min.js', '/bitrix/js/main/core/core.js'];
$content = preg_replace_callback(
'/<script\s([^>]*src=["\'][^"\']+["\'][^>]*)>/i',
function (array $m) use ($exclude): string {
foreach ($exclude as $ex) {
if (str_contains($m[0], $ex)) {
return $m[0];
}
}
if (str_contains($m[1], 'defer') || str_contains($m[1], 'async')) {
return $m[0];
}
return '<script ' . $m[1] . ' defer>';
},
$content
);
}
jQuery and Bitrix's core.js are excluded from defer — component initialization depends on them. Everything else receives defer.
Asset Manager and Dependency Groups
Since Bitrix 14+, \Bitrix\Main\Page\Asset is available. Scripts can be registered with a position specifier:
\Bitrix\Main\Page\Asset::getInstance()->addJs(
'/local/js/mymodule.js',
false, // do not bundle
\Bitrix\Main\Page\Asset::POS_AFTER // after </body>
);
POS_AFTER places the script before </body> — effectively equivalent to defer for independent modules. For scripts that need DOM-ready, this is preferable to async.
When defer Cannot Be Used
Scripts that cannot be deferred without consequences:
- jQuery, if other scripts in the page body call
$()inline - Bitrix's
core.jsandajax.js— the BX core - Analytics counters, if they measure time to interactivity
- A/B testing scripts (they modify the DOM before rendering)
For these, use <link rel="preload" as="script"> — the browser downloads the file with high priority, but execution happens in the order controlled by the developer.
Case Study: Travel Company Website
A site on Bitrix "Start" with a search form on the home page. TBT (Total Blocking Time) in Lighthouse — 1,840 ms. Cause: 12 scripts in <head>, including Swiper 8.1 (120 KB), Fancybox (80 KB), and Yandex Maps (async API, but blocking initialization).
After adding defer via OnEndBufferContent and migrating the map to deferred initialization via IntersectionObserver:
- TBT: 1,840 ms → 240 ms
- TTI (Time to Interactive): 6.1 s → 2.8 s
- Lighthouse Performance score: 34 → 78
Work: 1 day.







