Это предыстория к моей предыдущей статье «Обошёл блокировки в интернет магазине, но чуть не сломал корзину». Там я рассказал про автономный мониторинг на Playwright и n8n, который появился как следствие. Здесь - как именно появилась проблема, почему InSales не может её решить, и что я сделал своими руками пока ждал ответа от поддержки.

⚠️ Сразу честно: то что описано ниже - настоящий костыль, а не архитектурное решение. В процессе у меня ломалась корзина (товар добавлялся дважды), конфликтовали кастомные виджеты, пришлось переписывать логику загрузки нескольких компонентов. Если решитесь повторять - обязательно тестируйте на копии темы, и сразу настраивайте мониторинг корзины. Иначе узнаете о проблеме от покупателей, а не от системы.

Ссылка на GitHub с решением (ReadMe и инструкция к сниппетам на русском языке) - insales-yc-cdn-proxy

Трудности с загрузкой сайта

В конце мая - начале июня сайт перестал грузиться на некоторых устройствах, а если и загружался - не отображались картинки, системные иконки (корзина, сравнение и пр.). По Яндекс. Метрике заметно просела посещаемость, особенно на мобильных устройствах, баланс сместился в сторону ПК и планшетов. Открываю DevTools и вижу вот это:

jquery-3.5.1.min.js    Status: 0   Time: 68 000ms
common.v2.27.6.js      Status: 0   Time: 40 000ms
splide.min.js          Status: 0   Time: 68 000ms
lazyload.min.js        Status: 0   Time: 68 000ms
insales-icons.woff2    Status: 0   Time: 35 000ms

Статус 0 - это не HTTP-ошибка, это когда соединение вообще не дошло до сервера. Все файлы висят по 40-70 секунд и падают. Все с одного домена: static.insales-cdn.com.

Пишу в поддержку InSales. Ответ через несколько часов: "проблема на стороне провайдера, мы не можем повлиять, обратитесь к оператору связи". Только вот не совсем понятно, как оператор связи может помочь.

Ответ от поддержки
Ответ от поддержки

Прикладываю трассировку:

Трассировка МТС → static.insales-cdn.com (87.242.124.98):
  6     6 ms    as208677.asbr.router [212.188.45.6]
  7     *   *   *   Request timed out.
  8–30  *   *   *   Request timed out.

Обрыв на 7-м хопе внутри сети МТС. IP-адрес CDN попал под блокировку РКН. Проверяю с Мегафона - то же самое. Звонить провайдеру бессмысленно: они не будут разблокировать IP ради одного клиента.

Магазин фактически не работает для значительной части пользователей.

Что именно ломалось и почему

InSales раздаёт с static.insales-cdn.com всё что нужно для работы фронтенда:

  • jQuery - без него не работает ни один виджет платформы

  • common.js - ядро InSales: корзина, варианты товаров, API

  • Splide - слайдеры галереи и виджета доп. товаров

  • lazyload, bodyScrollLock, js.cookie - утилиты

  • micromodal, fslightbox - модальные окна и лайтбоксы

  • Шрифты Montserrat - основной шрифт большинства тем в нашем магазине

  • insales-icons - иконочный шрифт (корзина, поиск, меню)

  • Картинки товаров - часть изображений тоже через этот CDN

Когда всё это перестаёт загружаться сайт выглядит примерно так: текст без шрифтов (отображается системный), квадраты вместо иконок (либо вообще не отображаются), кнопки которые ни на что не реагируют. Технически страница отдаёт HTTP 200, то есть аптайм-мониторинг молчит. Но покупатель видит сломанный сайт.

Вот реальные числа из HAR конкурентов в тот же день:

Библиотека

Мой сайт (до)

Конкурент А

Конкурент Б

jQuery

68 000мс ❌

32 700мс ❌

не загрузился

common.js

40 000мс ❌

32 900мс ❌

40 000мс ❌

Splide

68 000мс ❌

24 500мс ❌

не загрузился

onLoad страницы

~2 минуты

47 секунд

21 секунда

Все три магазина на InSales. Все три сломаны в разной степени.

Почему нельзя просто подождать

Теоретически можно. РКН иногда разблокирует то что заблокировал "случайно". Но:

  1. Неизвестно сколько ждать. Может день, может месяц.

  2. Пока ждёшь - теряешь продажи каждый день.

  3. Даже после разблокировки это может запросто повториться. CDN не переехал, IP не сменился.

InSales в итоге прямо написала: "со стороны платформы мы на это повлиять не можем в данный момент". Это честный ответ - пакеты не доходят до их серверов до того как они вообще могут что-то сделать. Но магазину от этого не легче и клиентам этого не объяснишь.

Идея: Яндекс CDN как прозрачный прокси

Если РКН заблокировал IP 87.242.124.98, но блокирует его только "на последнем километре" у потребительских провайдеров, то что если между CDN InSales и пользователем поставить что-то российское и незаблокированное?

Яндекс Облако работает из российских дата-центров. Между серверами Яндекса и серверами InSales нет потребительского роутинга - значит нет и блокировки РКН. Яндекс CDN умеет проксировать запросы к произвольному origin.

Схема:

Браузер пользователя (МТС/Мегафон)
        │  ← заблокировано для прямого доступа
        ↓
assets.мой-сайт.ru  ←── Яндекс CDN edge (российский, не блокируется)
        │
        ↓
static.insales-cdn.com  ←── Origin InSales (недоступен напрямую,
                             но доступен из инфраструктуры Яндекса)

Проверил гипотезу: создал CDN-ресурс в YC с origin static.insales-cdn.com, открыл картинку через новый endpoint - загружается за 200мс. Гипотеза подтвердилась.

Реализация: два уровня защиты

Уровень 1 - Critical JS/CSS явно через прокси

widgets_assets это платформенный тег InSales, он подключает все библиотеки через CDN и его нельзя отключить из шаблона. Но можно загрузить те же файлы раньше, явно, через прокси.

В layout.html до {% widgets_assets %} добавил блок:

<link rel="preconnect" href="https://assets.мой-сайт.ru" crossorigin>

<!-- Critical JS — jQuery первым -->
<script src="https://assets.мой-сайт.ru/assets/static-versioned/4.81/static/libs/jquery/3.5.1/jquery-3.5.1.min.js"></script>
<script src="https://assets.мой-сайт.ru/assets/static-versioned/4.81/static/libs/splide/2.4.21/js/splide.min.js"></script>
<script src="https://assets.мой-сайт.ru/assets/static-versioned/4.81/static/libs/js-cookie/3.0.0/js.cookie.min.js"></script>
<script src="https://assets.мой-сайт.ru/assets/static-versioned/4.81/static/libs/body-scroll-lock/v3.1.3/bodyScrollLock.min.js"></script>
<script src="https://assets.мой-сайт.ru/assets/static-versioned/5.13/static/libs/vanilla-lazyload/17.9.0/lazyload.min.js"></script>
<!-- и остальные либы -->

Теперь браузер получает jQuery за 250мс вместо таймаута на 68 секунд. К моменту когда widgets_assets пытается загрузить свои версии - они уже в памяти, дублирование для большинства библиотек безопасно.

Уровень 2 - MutationObserver для всего остального

Картинки товаров, некоторые CSS, динамически добавляемые скрипты - widgets_assets вставляет их в DOM минуя шаблон. MutationObserver перехватывает каждый новый элемент и переписывает CDN URL на прокси до того как браузер успевает начать загрузку:

(function () {
  var CDN_FROM = 'static.insales-cdn.com';
  var CDN_TO = 'assets.мой-сайт.ru';

  function rewriteUrl(value) {
    if (!value || typeof value !== 'string') return value;
    return value.replaceAll(CDN_FROM, CDN_TO);
  }

  function rewriteElement(el) {
    if (!el || el.nodeType !== 1) return;
    ['src', 'data-src', 'data-original', 'href'].forEach(function(attr) {
      if (el.hasAttribute(attr)) {
        var old = el.getAttribute(attr);
        var next = rewriteUrl(old);
        if (next !== old) el.setAttribute(attr, next);
      }
    });
    if (el.hasAttribute('srcset')) {
      el.setAttribute('srcset', rewriteUrl(el.getAttribute('srcset')));
    }
    if (el.style && el.style.backgroundImage) {
      el.style.backgroundImage = rewriteUrl(el.style.backgroundImage);
    }
  }

  function rewriteAll(root) {
    (root || document).querySelectorAll(
      'img, source, script, link, [data-src], [data-original], [style]'
    ).forEach(rewriteElement);
  }

  rewriteAll(document);
  document.addEventListener('DOMContentLoaded', function() { rewriteAll(document); });

  new MutationObserver(function(mutations) {
    mutations.forEach(function(m) {
      m.addedNodes.forEach(function(node) {
        rewriteElement(node);
        if (node.querySelectorAll) rewriteAll(node);
      });
      if (m.type === 'attributes') rewriteElement(m.target);
    });
  }).observe(document.documentElement, {
    childList: true, subtree: true, attributes: true,
    attributeFilter: ['src', 'srcset', 'data-src', 'data-original', 'href', 'style']
  });
})();

Шрифты и иконки

system_v4_fonts ещё один платформенный тег, который подключает Montserrat и иконочный шрифт insales-icons. Его исходник закрыт, но содержимое видно в HAR: это stylesheet.css с относительными путями к woff2-файлам.

Решение: скачать оба stylesheet.css и все woff2 файлы, заменить пути на абсолютные URL Яндекс Storage, залить в публичный бакет, закомментировать оригинальный тег и подключить свои CSS-файлы.

{% comment %}{% include "system_v4_fonts" %}{% endcomment %}
<link rel="stylesheet" href="https://storage.yandexcloud.net/my-bucket/insales-icons-style.css">
<link rel="stylesheet" href="https://storage.yandexcloud.net/my-bucket/montserrat-stylesheet.css">

Не забудьте про CORS на бакете - без него браузер заблокирует загрузку шрифтов с другого домена.

Где чуть не сломалась корзина

Как я уже упоминал в предыдущей статье - поломки были. Главная: common.js (ядро InSales) загружался дважды. Наш через прокси загружался быстро, потом widgets_assets подгружал платформенную версию. InSales API инициализировался дважды, и товар добавлялся в корзину два раза на один клик.

Решение - не подключать common.js явно в critical-блоке, оставить его на откуп платформе. Остальные либы (jQuery, Splide, lazyload и т.д.) дублирование переносят нормально - они просто переписывают глобальную переменную.

Второй момент: кастомные виджеты у которых CDN URL был жёстко прописан внутри JS-кода (не в атрибутах DOM). MutationObserver такое не перехватывает - пришлось найти и исправить руками.

Третий: виджет анимированных баннеров на странице товара с акциями, новинками и т.п. Он загружался сразу при открытии страницы, хотя находится внизу. Lottie-анимации, AVIF-изображения и MP3-аудио суммарно добавляли 5-10 секунд к загрузке. После переноса на IntersectionObserver проблема ушла.

Результат в цифрах

Сравнение страницы товара с двумя конкурентами на InSales, холодная загрузка, МТС домашний, CDN заблокирован:

Метрика

Мой магазин

Конкурент А

Конкурент Б

onLoad

7.8 с

23.7 с

109.4 с

jQuery

250 мс YC

17 мс CDN

11 931 мс CDN

common.js

515 мс YC

4 189 мс CDN

10 932 мс CDN

Картинки avg

0.4 с

5.5 с

10.1 с

Корзина работает

✅ (частично)

Результат от 14.07.2026

Когда CDN полностью падает - у конкурентов сайт становится нефункциональным, а у нас работает.

Про стоимость

YC CDN с 1 июля 2026 года: 150 ₽/месяц фиксированная абонентская плата за ресурс, которая включает пакет трафика. Для магазина с 200-500 посетителями в день этого пакета хватает с головой, доплачивать не придётся. Сверх пакета - 1.054 ₽/ГБ.

Для крупных магазинов цифры будут другие, но порядок один и тот же - сотни рублей в месяц, не тысячи.

Подробный тариф на странице документации Яндекс CDN.

Применимость

Решение описано на примере InSales, но подход универсален для любой платформы где:

  • статика раздаётся с внешнего CDN который может попасть под блокировку

  • есть доступ к HTML-шаблонам (возможность вставить JS)

MutationObserver и прокси через YC CDN работают одинаково для Shopify, Битрикс, Tilda, WordPress с внешними CDN. Меняется только домен в CDN_FROM и список библиотек в critical-блоке.

Почему обязательно нужен мониторинг

Этот момент важен и я хочу на нём остановиться отдельно.

После внедрения у вас есть система которая зависит от конкретных версий библиотек на CDN, от порядка загрузки скриптов, от структуры объектов InSales API. Любое обновление платформы - потенциально тихо что-то ломает. Вы не узнаете об этом пока покупатель или менеджер не напишет что корзина не работает.

Именно поэтому я написал ecom-tech-speed-checker - автономный мониторинг на Playwright и n8n, который каждые 12 часов проверяет: открывается ли карточка товара, работает ли кнопка "в корзину", не добавляется ли товар дважды, корректна ли сумма в корзине. И шлёт отчёт в Telegram.

Без такого мониторинга этот костыль - пороховая бочка. С ним же - управляемый технический долг.

Что дальше

Исходники, сниппеты для InSales и CSS-файлы шрифтов с уже заменёнными путями — в репозитории:

insales-yc-cdn-proxy на GitHub

Если хотите повторить - начните с HAR своего сайта. В нём все версии ваших библиотек. Версии из репозитория могут не совпадать с вашей темой.

И обязательно тестируйте корзину после каждого шага.

Это вторая статья в серии про выживание интернет-магазина на InSales в условиях российской интернет-инфраструктуры. Первая - про автономный мониторинг.