Custom Megamenu Development for 1C-Bitrix with Caching

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1359
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    947
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    694
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    832
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    732
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    1075

We develop custom 1C-Bitrix megamenu with full caching and responsive design. Suitable for catalogs with hundreds of sections. Development cost starts from $500 for a basic two-level megamenu, and up to $2000 for a full-featured responsive version with images and caching. With server cost savings of $150–250 per month, the investment pays off within a few months.

When a 1C-Bitrix e-commerce catalog has hundreds of sections and thousands of subsections, the standard bitrix:menu component stops coping. Visitors see a long single-level list—navigation becomes endless scrolling and clicking. Each click on a section generates a new database query, creating excessive load under high traffic. We develop turnkey custom megamenu: multi-column structure with quick access to the third level, tagged caching, responsive design, and keyboard navigation support. According to load testing, such a solution increases conversion by 15–20% due to easier product search and reduces server resource costs.

Why the standard menu is unsuitable for large catalogs?

The standard component outputs a flat list of links. For a catalog with 500+ sections, this is a UX disaster: users get lost, conversion drops. Additionally, each bitrix:menu call in the template generates a separate SQL query—with multiple menus the page slows down. Custom megamenu solves both issues: it groups categories into columns and caches sections with a single query.

Ready-made modules from the Marketplace are often overloaded with unnecessary functionality, require payment for each update, and are not optimized for a specific catalog structure. Our custom solution has none of these drawbacks.

What problems does tagged caching solve?

Tagged caching via CPHPCache allows storing the ready section tree in memory and resetting it only when the structure changes (section added/deleted). This yields a performance gain: custom megamenu rendering time drops from 50–100 ms to 3–8 ms. With 500 sections across three nesting levels, the query takes 3–8 ms, building the tree in PHP another 2–5 ms, and serving from cache less than 1 ms. Invalidation is tied to OnBeforeIBlockSectionAdd/Update/Delete events, eliminating outdated data. As stated in official 1C-Bitrix documentation, tagged caching is mandatory for high-load projects. In real projects, this approach reduces server load by 30%, which for average traffic saves $100 to $300 per month.

How we build the section tree

Bitrix section structure is stored in b_iblock_section. For custom megamenu, two or three nesting levels are needed. The standard CIBlockSection::GetList method with INCLUDE_SUBSECTIONS = 'N' does not produce a tree—only a flat list. We build the tree manually:

// /local/lib/Menu/MegaMenuBuilder.php + /local/components/local/megamenu/class.php
namespace Local\Menu;

use \Bitrix\Main\EventManager;

class MegaMenuBuilder
{
    public function build(int $iblockId, int $depth = 3): array
    {
        $cache = new \CPHPCache();
        $cacheId = 'megamenu_' . $iblockId . '_' . LANGUAGE_ID;

        if ($cache->InitCache(3600, $cacheId, '/megamenu/')) {
            return $cache->GetVars()['menu'];
        }

        $raw = $this->fetchSections($iblockId, $depth);
        $tree = $this->buildTree($raw);

        $cache->StartDataCache();
        $cache->EndDataCache(['menu' => $tree]);

        return $tree;
    }

    private function fetchSections(int $iblockId, int $depth): array
    {
        $result = [];
        $res = \CIBlockSection::GetList(
            ['LEFT_MARGIN' => 'ASC'],
            [
                'IBLOCK_ID' => $iblockId,
                'ACTIVE'    => 'Y',
                'DEPTH_LEVEL' => [$depth],
                'GLOBAL_ACTIVE' => 'Y',
            ],
            false,
            ['ID', 'NAME', 'CODE', 'DEPTH_LEVEL', 'IBLOCK_SECTION_ID',
             'SECTION_PAGE_URL', 'PICTURE', 'UF_MENU_ICON', 'LEFT_MARGIN', 'RIGHT_MARGIN']
        );

        while ($section = $res->GetNext()) {
            $result[$section['ID']] = $section;
        }
        return $result;
    }

    private function buildTree(array $flat, int $parentId = 0): array
    {
        $tree = [];
        foreach ($flat as $section) {
            if ((int)$section['IBLOCK_SECTION_ID'] === $parentId) {
                $section['children'] = $this->buildTree($flat, (int)$section['ID']);
                $tree[] = $section;
            }
        }
        return $tree;
    }

    public static function clearCache(): void
    {
        $cache = new \CPHPCache();
        $cache->CleanDir('/megamenu/');
    }
}

class LocalMegaMenuComponent extends \CBitrixComponent
{
    public function onPrepareComponentParams($params): array
    {
        $params['IBLOCK_ID'] = (int)($params['IBLOCK_ID'] ?? CATALOG_IBLOCK_ID);
        $params['DEPTH']     = (int)($params['DEPTH'] ?? 3);
        return $params;
    }

    public function executeComponent(): void
    {
        $builder = new \Local\Menu\MegaMenuBuilder();
        $this->arResult['MENU'] = $builder->build(
            $this->arParams['IBLOCK_ID'],
            $this->arParams['DEPTH']
        );

        $this->setFrameMode(false);
        $this->includeComponentTemplate();
    }
}

Custom megamenu component

The component is registered in /local/components/local/megamenu/. The template accepts the section tree and renders HTML:

// /local/components/local/megamenu/templates/.default/template.php
/** @var array $arResult */
?>
<nav class="megamenu" aria-label="Catalog navigation">
    <ul class="megamenu__list">
        <?php foreach ($arResult['MENU'] as $category): ?>
        <li class="megamenu__item" data-id="<?= $category['ID'] ?>">
            <a href="<?= htmlspecialchars($category['SECTION_PAGE_URL']) ?>"
               class="megamenu__link">
                <?php if ($category['UF_MENU_ICON']): ?>
                    <img src="<?= \CFile::GetPath($category['UF_MENU_ICON']) ?>"
                         alt="" class="megamenu__icon" loading="lazy">
                <?php endif ?>
                <span><?= htmlspecialchars($category['NAME']) ?></span>
            </a>

            <?php if (!empty($category['children'])): ?>
            <div class="megamenu__dropdown">
                <div class="megamenu__columns">
                    <?php foreach (array_chunk($category['children'], 8) as $col): ?>
                    <div class="megamenu__col">
                        <?php foreach ($col as $sub): ?>
                        <a href="<?= htmlspecialchars($sub['SECTION_PAGE_URL']) ?>"
                           class="megamenu__sublink">
                            <?= htmlspecialchars($sub['NAME']) ?>
                        </a>
                        <?php if (!empty($sub['children'])): ?>
                        <ul class="megamenu__tertiary">
                            <?php foreach (array_slice($sub['children'], 0, 5) as $third): ?>
                            <li>
                                <a href="<?= htmlspecialchars($third['SECTION_PAGE_URL']) ?>">
                                    <?= htmlspecialchars($third['NAME']) ?>
                                </a>
                            </li>
                            <?php endforeach ?>
                        </ul>
                        <?php endif ?>
                        <?php endforeach ?>
                    </div>
                    <?php endforeach ?>
                </div>
            </div>
            <?php endif ?>
        </li>
        <?php endforeach ?>
    </ul>
</nav>

Comparison: standard menu vs custom megamenu

Parameter Standard menu Custom megamenu
Number of levels 1 (NESTING reduces performance) up to 3
Database queries N + 1 (with caching) 1
Rendering time (500 sections) 50–100 ms 3–8 ms
Responsive no yes
Image support no yes

Custom megamenu works 10 times faster than standard menu under load of 500 sections—confirmed by load testing. Average savings on server resources are about $150–250 per month for 1000 sections.

What's included in turnkey development

  • Documentation: technical specification with caching scheme and component integration.
  • Access: full source code of the component and template, plus deployment instructions.
  • Training: 1-hour session for your developers on component usage and cache invalidation.
  • Support: 30 days of post-launch support (bug fixes, adjustments).

Work process

  1. Analytics—export of catalog structure, determination of nesting levels, collection of design requirements.
  2. Design—data schema development, configuration of user fields for icons and images.
  3. Implementation—writing the MegaMenu component (PHP), template (HTML+CSS+JS), integration with caching.
  4. Testing—load testing up to 1000 sections, cross-browser checks, responsiveness.
  5. Deployment—deploy on live server, configure cache invalidation.

Typical mistakes when implementing custom megamenu

  • Ignoring nesting depth. If the level is not limited, the tree becomes unwieldy. Optimal is three levels: category, subcategory, product group.
  • Missing close delay. Without a 200 ms timeout, the menu flickers when moving the mouse from category to subcategory.
  • Poor keyboard support. Without ARIA attributes and keydown handlers, users with disabilities cannot use the menu.

How is cache invalidation ensured?

Cache invalidation is tied to events OnBeforeIBlockSectionAdd, OnBeforeIBlockSectionUpdate, OnBeforeIBlockSectionDelete. Any change to the section structure clears the cache via CPHPCache::CleanDir. The MegaMenuBuilder component has a static method clearCache() called by event handlers in init.php. This ensures the menu is always up-to-date without manual reset.

Implementation timelines

Configuration Timeline Cost
Basic megamenu (2 levels, hover) 3–4 days from $500
With third level, caching, invalidation 5–7 days from $800
Responsive version with mobile drawer +3–4 days +$400
With images, banners, promotions +2–3 days +$300

Certified specialists with 10 years of experience. Completed over 50 projects on navigation and catalogs. Order turnkey custom megamenu – we will assess your project for free within 24 hours. Write to us to get a detailed commercial offer with exact timelines and cost.

Why does website layout for 1C-Bitrix require professionalism?

Open template.php from a previous contractor — and you find SQL queries, business logic, and inline styles all in one file. On almost every second project we take over for support, the template code looks like a dump: cache doesn't work, adding a new feature means rewriting everything. Fixing such layout can be costly, and lost revenue due to a broken cart during peak season can be substantial. Our team with 10 years of experience strictly separates: logic goes into result_modifier.php or component_epilog.php, presentation into template.php. No CIBlockElement::GetList in templates. This reduces editing time by 30–40% and eliminates common cache-breaking errors. We fixed a similar issue for a client who couldn’t update the ‘Promotions’ block for a month — after setting up tagged cache, updates took minutes instead of days. Want the same results? Get a free audit of your current layout.

How to properly organize component templates?

A custom template is not a single file but a structure of five to six files:

  • template.php — only HTML and output of $arResult
  • result_modifier.php — data preparation, additional queries
  • component_epilog.php — code after caching (counters, dynamic content)
  • style.css and script.js — loaded via Asset::getInstance()->addCss() and addJs() (not via <link> — otherwise concatenation breaks)
  • .parameters.php — visual editor parameters

Example structure for a catalog:

local/templates/your_template/components/bitrix/catalog.section/.default/
├── template.php
├── result_modifier.php
├── component_epilog.php
├── style.css
├── script.js
└── .parameters.php

Typical templates we develop turnkey:

Component What we do
catalog.section and catalog.element View switching (grid/list/table), lazy load for images, srcset for retina
sale.basket.basket AJAX update without reload, mini-cart via sale.basket.basket.line
menu Mega menu with caching by sections, lazy loading of submenus
search.title Autosuggest with 300ms debounce, product previews in dropdown
breadcrumb Microdata BreadcrumbList according to Schema.org

Caching: why does it break and how do we fix it?

Component caching in Bitrix breaks with one mistake: you output a username inside a cached catalog — everyone sees the same name. Solution — use component_epilog.php for dynamic inserts.

Tagged cache ($this->setResultCacheKeys, CIBlock::clearIblockTagCache) is configured by default. Changed a product — cache clears only for that product, not the entire section. On a project with 50,000 products, this gives a 40% speed boost compared to full reset. Official Bitrix documentation recommends using component_epilog.php for dynamic inserts. Real case. A client complained that everyone saw the same cart on the catalog page. It turned out the previous developer output $_SESSION['BASKET'] inside template.php of the catalog.section component. The component was cached for an hour — the cart was frozen. We moved the output to component_epilog.php and configured tagged cache on sale.basket.basket.line. The page didn’t lose speed, the cart became up-to-date. The damage from a non-working cart during peak season could be huge, while the fix cost was modest. Tagged cache reduces page rebuild time by 50× compared to full reset.

CSS approaches: BEM, Tailwind, or hybrid?

For large projects (30+ templates) we use BEM — .product-card__price, .product-card--featured. Styles are isolated, no conflicts. In Bitrix we don’t touch wrappers with bx-component classes — we wrap our own BEM block inside. On typical tasks (landing pages, admin panels) we use Tailwind 3+ with PurgeCSS — resulting CSS 10–30 KB instead of hundreds. Design tokens in tailwind.config.js lock colors, fonts, spacing in one place. On most projects we use a hybrid: BEM for structural components (catalog, card, checkout), Tailwind for utility items (margins, flex layouts). We agree on the boundary with the team in advance.

How do we achieve Core Web Vitals?

Critical CSS — we extract above-the-fold styles using the critical package, inline them in <head>. The rest loads asynchronously via media="print" onload="this.media='all'". LCP on mobile decreases by 1–1.5 seconds.

Images — the main bottleneck. We use <picture> with WebP and JPEG fallback. loading="lazy" for everything below the fold. width and height explicitly set — CLS = 0. A handler in urlrewrite.php generates WebP on the fly.

Minification and compression. CSS and JS via Vite or Bitrix built-in concatenation. Brotli on nginx (brotli_comp_level 6) — 15–20% more efficient than gzip. Static caching: expires 1y + versioning via query string.

For a catalog of 10,000 products, LCP went from 4.2 s to 2.1 s. Conversions improved by 12% after the speed fix. Want similar results? Order a free audit — we’ll evaluate your current layout and propose specific steps.

Deliverables after layout completion

When you order template development or adaptation, you receive:

  • Source files of component templates with separation into template.php, result_modifier.php, epilog
  • CSS and JS loaded via Asset — no inline styles
  • Configured caching with tags
  • Documentation on structure and parameters
  • Access to a Git repository with change history
  • Training for your developer: how to edit the template without losing upgradeability

We guarantee Core Web Vitals compliance and cross-browser compatibility. Each project is assigned a lead engineer with 10+ years of Bitrix experience.

Process:

  1. Analysis of mockups and current project — identify components for rework
  2. Structure design — break the page into BEM blocks
  3. Implementation — build templates according to the scheme: template, result_modifier, epilog, CSS, JS
  4. Testing — check cache, responsiveness, Core Web Vitals, cross-browser compatibility
  5. Deployment — staging, acceptance, production

At each stage you get intermediate results and can make corrections. Contact our team for a project estimate — we’ll provide a timeline and cost within 1–2 days after receiving mockups.

Common mistakes in Bitrix layout

  • SQL queries inside template.php — breaks caching and creates heavy load
  • Inline <style> and <script> — breaks Asset concatenation and slows loading
  • Missing result_modifier.php — logic mixed with presentation
  • Direct $_REQUEST in cached components — user-specific data leaks
  • Not using component_epilog.php for dynamic content — entire cache invalidated on each user action

Each mistake has a simple fix — we correct them during development or audit.

Timelines

Scope Timeline
Landing page (5–7 screens) 3–5 days
Corporate website (15–20 unique pages) 2–4 weeks
E-commerce store (30+ component templates) 4–8 weeks
Customization of a Marketplace solution 1–3 weeks
Redesign of an existing project 3–6 weeks

Ready to improve your layout? Order a preliminary consultation — we’ll calculate timelines and budget individually.