CSS and JS File Bundling for 1C-Bitrix
On a project developed over several years by multiple teams, a typical situation arises: every module, every component, every widget includes its own CSS and JS files. Chrome DevTools → Network shows 60–120 requests for stylesheets and scripts alone. Even with HTTP/2 (which theoretically multiplexes requests), this creates overhead: DNS lookups, TCP handshakes, TLS negotiation for each domain, and request prioritization. On mobile networks, 100 small requests are always worse than 5 large ones.
Bitrix Built-in Bundling Mechanism
Bitrix provides two tools for file bundling: via CMain::AddCSSLink() / CMain::AddHeadScript() with minification enabled (Settings → Performance → Compression), and via the bitrix:main.include component with bundling parameters.
With minification enabled, Bitrix groups CSS into one file /bitrix/cache/css/<hash>.css and JS into /bitrix/cache/js/<hash>.js. The key limitation: only files registered through the PHP API before ShowHead() is called are bundled. Files included directly via <link> in templates will not be part of the bundle.
Problems and Solutions
Include order. Bundling changes the order of CSS rules, which breaks styles that rely on cascade specificity. Solution: explicitly control order via \Bitrix\Main\Page\Asset::addCss() with priorities, or split into two bundles — "base" (reset, grid, typography) and "component".
Inline styles and scripts. Many Bitrix components output <style> and <script> tags directly into HTML — these are not bundled automatically. For custom components, move them to files via $this->addExternalCSS() and $this->addExternalJS().
Conditional loading. Some scripts are only needed on specific pages (form editor, homepage slider, contact page map). Bundling everything into one file increases its size. The right approach: split into a critical bundle (loaded everywhere) and page-specific bundles (loaded conditionally).
Webpack/Vite as an Alternative to Built-in Tools
For projects with active frontend development, Bitrix's built-in bundler is not enough. The modern approach is to build via Webpack or Vite with a dedicated webpack.config.js for the Bitrix template. Benefits: tree shaking (removal of unused code), code splitting, SCSS/LESS processing, TypeScript transpilation.
Example structure for Vite + Bitrix template:
local/templates/mytemplate/
├── src/
│ ├── css/
│ │ ├── main.scss
│ │ └── components/
│ ├── js/
│ │ ├── app.js
│ │ └── pages/
├── dist/ ← Vite build output
│ ├── app.[hash].css
│ └── app.[hash].js
├── header.php ← loads from dist/
└── vite.config.js
header.php includes files from dist/ via CMain::AddCSSLink() — so they participate in Bitrix's standard caching mechanism.
Case Study: Portal with Multiple Development Teams
A B2B portal, 5 years in development, three different teams. On the catalog page: 47 CSS requests (890 KB uncompressed), 68 JS requests (1.4 MB). Audit revealed: 12 CSS files were duplicated (the same normalizers included in different components), 8 JS libraries loaded in multiple versions simultaneously (jQuery 1.9, 2.1, and 3.6 on the same page).
What was done:
- Audit of all includes by intercepting
CMain::AddCSSLink()andAddHeadScript()with logging - Library unification: single jQuery version, removal of duplicates
- Migration of all direct
<link>tags to the Bitrix API - Vite build configuration for new code, legacy packaged into a single bundle
- Code splitting: critical bundle + 4 page-specific bundles
Result: 47 CSS requests → 3, 68 JS requests → 5. Total CSS+JS weight (after gzip) dropped from 850 KB to 210 KB due to deduplication and tree shaking.
Timeline
| Project Type | Scope | Duration |
|---|---|---|
| Simple site (1 template, <30 components) | Migrate includes to API, enable minification | 1–2 days |
| Mid-size project (custom frontend, multiple templates) | Audit + library unification + build setup | 3–7 days |
| Large portal (multiple teams, legacy code) | Full audit, include refactoring, Webpack/Vite setup | 7–20 days |
Load testing after bundling is mandatory — in rare cases script load order matters, and violating it causes JS errors on specific pages.







