Мой блог по программированию

⚡ Кейс: Как спасти интернет-магазин после краха на Bitrix. Переезд на Tilda с интеграцией 1С и кастомным кодом

Всем привет!
Сегодня расскажу историю реанимации бизнеса, который оказался на грани потери онлайн-продаж.
Ко мне обратился владелец интернет-магазина «Бостон обмундирование». Исходная ситуация была критической: старый сайт на Bitrix перестал работать. Анализ показал, что восстановить его невозможно — устаревший движок и уязвимости в шаблоне Аспро, которые годами не обновлялись, привели к фатальному сбою. Резервных копий не было, хостинг развел руками.

Стратегическое решение: почему Tilda?

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

Этап 1. Синхронизация с 1С: Автоматизация остатков

Главная задача современного интернет-магазина — актуальные данные. Мы настроили выгрузку товаров напрямую из 1С клиента. Использовали стандарт CommerceML (см. рис. 1. ниже) — это позволило автоматизировать процесс и избежать ручного ввода тысяч позиций. Настройка прошла бесшовно: я подготовил инструкции для клиента, и он самостоятельно запустил выгрузку на стороне 1С.

Рис. 1.

Этап 2. Визуальный перфекционизм и UX-доработка

При выгрузке из 1С возникла типичная проблема: изображения товаров имели разное кадрирование, из-за чего сетка каталога выглядела неаккуратно. Чтобы не заставлять клиента переделывать тысячи фото вручную, я решил задачу программно.
Для блока ST320N я присвоил классу название "uc-katalog-st320n" и внедрил CSS-решение, которое привело карточки к единому стильному виду, добавило мягкие радиусы и приятный фоновый акцент.
Код для стилизации карточек (блок T123):
<!-- Правим карточки каталога -->

<style>

    /*Радиус в фильтре карточки*/
    .uc-katalog-st320n .t951 .js-store-cont-w-filter .t951__cont-wrapper .js-store-parts-select-container {
        border-radius: 15px;
    }
    
    /*Фон для картинки карточки*/
    .uc-katalog-st320n .t951 .js-store-cont-w-filter .t951__cont-wrapper .t951__grid-cont > div > div > a {
        background-color: #f6f6f6;
        border-radius: 15px 15px 0 0;
        padding: 15px 15px 0 15px;
    }
    
    /*Фон в описании карточки*/
     .t951 .js-store-cont-w-filter .t951__cont-wrapper .t951__grid-cont  > div > div > div {
        background-color: #f6f6f6;
        border-radius: 0 0 15px 15px;
        padding: 0 15px 15px 15px;
    }
    
    @media (min-width: 768px) {
        /*Выравниваем кнопку Избранное для ПК*/
        .uc-katalog-st320n .t951 .js-store-cont-w-filter .t951__cont-wrapper .t951__grid-cont .t-store__card__btns-wrapper a.t1002__addBtn {
            margin-top: -6px;
        }
    }
    
    /*Выравниваем картинку по центру на мобильном устройстве*/
    .uc-katalog-st320n .t951 .js-store-cont-w-filter .t951__cont-wrapper .t951__grid-cont > div > div > a > div {
        text-align: center;
    }

</style>
В итоге получаем серый фон у карточек, см. рис. 2 ниже, и отображение картинок в полный размер, что важно для любого интернет-магазина.

Рис. 2.

Этап 3. Исправление навигации: удобство для покупателя

В стандартном блоке Tilda обнаружился баг: при клике на название корневой категории (например, «Одежда зимняя») пользователь видел надпись «Ничего не найдено», так как товары привязаны только к подкатегориям. Это прямой путь к потере конверсии.
Я написал скрипт, который меняет логику: теперь при клике на название категория просто раскрывается, показывая вложенные подразделы. Это интуитивно понятно и не путает покупателя.
Итоговый объединенный рабочий код для ПК и для Мобильного, при клике убираем надпись "Ничего не найдено" (код указываем в блок T123):
<!-- Объединенный рабочий код для ПК и для Мобильного -->

<script>
function initializePerfectlyUnifiedFix() {
    
    // Даем Tilda время, чтобы создать все элементы.
    setTimeout(function() {
        
        // Вешаем "неуязвимого" слушателя на весь ДОКУМЕНТ.
        document.addEventListener('click', function(event) {
            
            // ШАГ 1: ГЛАВНАЯ И ЕДИНАЯ ПРОВЕРКА.
            // Если клик был не внутри нашего каталога, полностью его игнорируем.
            if (!event.target.closest('.uc-katalog-st320n')) {
                return;
            }

            // Теперь, когда мы уверены, что находимся в нужном блоке,
            // применяем логику в зависимости от ширины экрана.

            // --- Логика для ПК (экраны > 960px) ---
            if (window.innerWidth > 960) {

                const titleDiv = event.target.closest('.t-store__parts-tree-btn-title');
                if (!titleDiv) { return; }

                const parentNode = titleDiv.closest('.t-store__parts-tree-node');
                if (!parentNode || !parentNode.querySelector('.t-store__parts-tree-children')) {
                    return;
                }

                const expanderButton = parentNode.querySelector('.t-store__parts-tree-expander');

                if (expanderButton) {
                    event.preventDefault();
                    event.stopPropagation();
                    expanderButton.click();
                }

            // --- Логика для Мобильных (экраны <= 960px) ---
            } else {

                const parentItem = event.target.closest('.t-store__parts-item');

                if (!parentItem || !parentItem.classList.contains('t-store__parts-item_has-subparts') || event.target.closest('.t-store__parts-item-arrow')) {
                    return;
                }

                event.preventDefault();
                event.stopPropagation();

                const arrow = parentItem.querySelector('.t-store__parts-item-arrow');
                if (arrow) {
                    const clickEvent = new MouseEvent('click', {
                        bubbles: true,
                        cancelable: true,
                        view: window
                    });
                    arrow.dispatchEvent(clickEvent);
                }
            }
        }, true);

        //console.log('Gemini (Unified Fix v2.0): Исправленный единый скрипт активен.');

    }, 500);
}

// Запускаем после полной загрузки страницы.
window.addEventListener('load', initializePerfectlyUnifiedFix);
</script>

Этап 4. Подготовка к SEO и рекламе: умная кнопка «Назад»

Для эффективного продвижения важно, чтобы каждый товар имел свою уникальную страницу, индексируемую поисковиками. Мы отказались от модальных окон в пользу отдельных страниц с полноценным хедером и футером.
Но возникла проблема юзабилити: стандартная функция «назад» (см. код ниже) в браузере часто сбивает пользователя, особенно если он зашел по прямой ссылке из рекламы или взаимодействовал с корзиной.
Код стандартной функции «назад»:
<script>
    $('a[href="#back"]').click(function() {
        history.back();
    return false;
    })
</script>
Чтобы путь клиента был бесшовным, я реализовал кастомную логику кнопки «Вернуться назад» (см. рис. 3 ниже).

Рис. 3.

Сначала присвоил блоку ME605 (хлебные крошки) класс "uc-header-tovar-back" и оформим ссылку визуально кодом (код указываем в блок T123):
<!-- Подчеркиваем ссылку возврата назад -->
<style>
    .t-records .uc-header-tovar-back .t758 .t-container .t758__col a.t-menu__link-item {
        border-bottom: 1px solid #3366B9;
        padding-bottom: 1px;
        display: inline-block;
    }
    
    /*При наведении*/
    .t-records .uc-header-tovar-back .t758 .t-container .t758__col a.t-menu__link-item:hover {
        border-bottom: 1px solid #9e9e9e;
    }
</style>
Затем внедрил скрипт, который понимает: если пользователь пришел с другой страницы сайта — нужно вернуть его именно в то место каталога, где он был. А если это прямой заход (например, из Яндекс.Директа) — кнопка корректно переведет его в общий каталог, а не «в никуда».
Код для всех браузеров (кроме Firefox) (код указываем в блок T123):
<script>
/* Выполняем код только если это НЕ Mozilla Firefox */
if (typeof InstallTrigger === 'undefined') {


/*<!-- Ссылка "Вернуться назад" в отдельном окне товара -->*/


(function () {

  var waiting = false;

  /*Проверка клика по корзине и избранному - игнорируем их*/
  function isTrash() {
    return (
      location.hash.indexOf('tcart') !== -1 ||
      location.hash.indexOf('addtofavorites') !== -2
    );
  }


  /*Проверка прямой ссылки в карточку товара*/
  $('a[href="#back"]').click(function () {
      if (waiting) return false;

      waiting = true;

      /*сюда вставим проверку*/
      if (document.referrer.indexOf(location.origin) === 0) {
          // если предыдущая страница была на этом сайте — делаем back
          history.back();
      } else {
          // если прямой заход — кидаем на каталог
          location.href = '/katalog';
      }

      return false;
  });


  window.addEventListener('popstate', function () {
    if (!waiting) return;

    // если после первого back мы всё ещё в корзине / избранном
    if (isTrash()) {
      history.back();
    }

    waiting = false;
  });

})();


}
</script>
Отдельное решение для Mozilla Firefox (код указываем в блок T123):
<script>
/* Выполняем код только в Mozilla Firefox */
if (typeof InstallTrigger !== 'undefined') {



/*<!-- Ссылка "Вернуться назад" в отдельном окне товара — версия под Mozilla Firefox -->*/

(function () {
  // Запускаем исключительно в Firefox
  var isFirefox = typeof InstallTrigger !== 'undefined';
  if (!isFirefox) return;

  var waiting = false;

  /*Проверка клика по корзине и избранному - игнорируем их*/
  function isTrash() {
    return (
      location.hash.indexOf('tcart') !== -1 ||
      location.hash.indexOf('addtofavorites') !== -2
    );
  }

  /*Проверка прямой ссылки в карточку товара*/
  $(document).on('click', 'a[href="#back"]', function () {
    if (waiting) return false;

    waiting = true;

    /*сюда вставим проверку*/
    if (document.referrer.indexOf(location.origin) === 0) {
      // если предыдущая страница была на этом сайте — делаем back
      history.back();
    } else {
      // если прямой заход — кидаем на каталог
      location.href = '/katalog';
    }

    return false;
  });

  // В Firefox для hash-навигации нужно отслеживать hashchange, а не popstate
  window.addEventListener('hashchange', function () {
    if (!waiting) return;

    // если после первого back мы всё ещё в корзине / избранном — делаем ещё один back
    if (isTrash()) {
      history.back();
      return; // waiting оставляем true до следующего hashchange
    }

    waiting = false;
  });

})();




}
</script>

Итог

Заказчик остался доволен (см. рис. 4 ниже) и получил современный, быстрый и технически совершенный интернет-магазин, который готов к масштабному запуску рекламы. Мы не просто перенесли сайт, мы сделали его лучше, удобнее и надежнее.
Оценить результат моей работы и удобство интерфейса интернет-магазина вы можете по ссылке: formstore.ru/katalog.
Нужен технически грамотный интернет-магазин, который работает на бизнес, а не просит обновлений? Давайте обсудим ваш проект!


Рис. 4.

P.S. Спустя месяц выяснилось, что сайт по запросу "Обмундирование в Екатеринбурге" на первом месте в органической выдаче (см. рис. 5 ниже) и ИКС увеличился на 50 пунктов (см. рис. 6 ниже).

Рис. 5.

Рис. 6.