Это предыстория к моей предыдущей статье «Обошёл блокировки в интернет магазине, но чуть не сломал корзину». Там я рассказал про автономный мониторинг на 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. Все три сломаны в разной степени.
Почему нельзя просто подождать
Теоретически можно. РКН иногда разблокирует то что заблокировал "случайно". Но:
Неизвестно сколько ждать. Может день, может месяц.
Пока ждёшь - теряешь продажи каждый день.
Даже после разблокировки это может запросто повториться. 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 в условиях российской интернет-инфраструктуры. Первая - про автономный мониторинг.
