Ускорение карточки товара - состав блоков, отложенная загрузка
Ускоряем карточку товара - самую посещаемую страницу магазина: замер состава, отложенная загрузка тяжёлых блоков, кэш и проверка результата.
Что нужно знать заранее
Карточка товара обрастает новыми блоками совершенно незаметно для команды. Рекомендации, отзывы, наличие по складам, просмотренные товары и баннеры добавляются по одному, а тормозить начинает страница целиком.
Далеко не всё, что есть на странице, нужно посетителю сразу. Блоки ниже первого экрана можно загрузить отдельным запросом после отрисовки, и посетитель этого даже не заметит.
Замер делают до правок и обязательно повторяют после них. Число запросов к базе и время сборки страницы дают честное сравнение, а ощущение «стало быстрее» проверку не проходит.
Шаги
- Замерить число запросов и время сборки карточки товара на живых данных каталога.
- Разобрать страницу на блоки и оценить вклад каждого в это время.
- Вынести тяжёлые блоки ниже первого экрана в отдельный запрос после первой отрисовки.
- Закрыть кэшем те блоки, которые одинаковы для всех посетителей витрины.
- Повторить замер на второй загрузке страницы и сравнить полученные числа с исходными.
Решение
Считаем запросы и время сборки:
$conn = \Bitrix\Main\Application::getConnection();$conn->startTracker();$APPLICATION->IncludeComponent('bitrix:catalog.element', '', $params);printf("запросов: %d, время: %.2f c\n", count($conn->getTracker()->getQueries()), $conn->getTracker()->getTime());$conn->stopTracker();Число запросов важнее секунд на стенде разработчика. Сотня запросов в карточке почти всегда означает выборку в цикле, и она заметна на боевом сервере под нагрузкой.
Выносим тяжёлый блок в отдельный запрос:
const box = document.querySelector('#recommendations');new IntersectionObserver(([entry], observer) => { if (!entry.isIntersecting) { return; } fetch('/local/ajax/recommendations.php?id=' + box.dataset.id) .then(r => r.text()).then(html => { box.innerHTML = html; }); observer.disconnect();}).observe(box);Блок грузится в момент, когда посетитель до него доскроллил. Для рекомендаций, отзывов и просмотренных товаров это правильное поведение: их видят далеко не все посетители.
Кэшируем блок с тегом каталога:
$cache = \Bitrix\Main\Data\Cache::createInstance();if ($cache->initCache(3600, 'card_extra_' . $productId, '/vendor/card')) { $data = $cache->getVars();} elseif ($cache->startDataCache()) { $tagged = \Bitrix\Main\Application::getInstance()->getTaggedCache(); $tagged->registerTag('iblock_id_' . $iblockId); $data = collectExtraBlocks($productId); $cache->endDataCache($data);}Готовим картинки к быстрой отрисовке:
$thumb = \CFile::ResizeImageGet($fileId, ['width' => 700, 'height' => 700], BX_RESIZE_IMAGE_PROPORTIONAL, true);?><img src="<?= $thumb['src'] ?>" width="700" height="700" loading="lazy" alt=""><?php// размеры в разметке убирают скачок вёрстки, ленивая загрузка экономит трафикПроверяем результат на второй загрузке:
curl -s -o /dev/null -w 'первая: %{time_total}\n' https://example.com/catalog/product/curl -s -o /dev/null -w 'вторая: %{time_total}\n' https://example.com/catalog/product/# первая загрузка собирает кэш, вторая показывает обычное время для посетителяТипичные проблемы
Карточка выполняет сотни запросов к базе данных.
Данные торговых предложений или свойств запрашиваются в цикле по вариантам. Нужные данные забирают одной общей выборкой на всю карточку сразу.
Страница ждёт ответа внешнего сервиса рекомендаций.
Блок рекомендаций собирается прямо в момент отрисовки страницы и без всякого таймаута. Такие блоки грузят отдельным запросом и жёстко ограничивают время ожидания ответа.
После включения кэша посетители видят чужие цены.
Ключ кэша не учитывает группы покупателя, а цены у разных групп различаются. В ключ кэша добавляют набор групп либо выносят цену в динамическую часть.
Замер на стенде показывает быстро, а на бою медленно.
На стенде сотня товаров, а на боевом сайте десятки тысяч и живая нагрузка. Замеряют на копии боевых данных или прямо на бою в спокойное время.
Вёрстка прыгает при загрузке картинок товара.
У изображений не заданы размеры, и браузер узнаёт их только после загрузки. Ширину и высоту картинки указывают в разметке вместе с ленивой загрузкой.
Частые вопросы
С чего начинать ускорение карточки?
С замера числа запросов и времени сборки на живых данных каталога. Без этих чисел любые правки остаются догадками о причине.
Что можно грузить отдельным запросом?
Всё, что ниже первого экрана: рекомендации, отзывы, просмотренные товары, наличие по складам. Посетитель этого не замечает, а страница отдаётся заметно раньше.
Как кэшировать блоки с ценами?
Включать в ключ кэша набор групп покупателя и сбрасывать кэш тегом каталога. Иначе оптовик получит розничную цену из чужой готовой копии.
Помогает ли композит на карточке?
Помогает, но требует аккуратности: цена и наличие обязаны быть динамическими. Иначе покупатель увидит числа, собранные для другого посетителя.
Сколько запросов считается нормой?
Ориентир для карточки - несколько десятков, а не сотни. Точное число зависит от проекта, но сотня почти всегда означает выборку в цикле.
Смежное
- Медленный сайт - оглавление подтемы
- Сайт тормозит: разбор причин по убыванию частоты - общий разбор медленной витрины
- Кэш не срабатывает: страница собирается заново каждый раз - когда кэша нет вовсе
- Тегированный кэш: сброс по изменению данных, а не по расписанию - как устроен сброс по тегу
- Быстрые картинки на витрине: размеры, ленивая загрузка, WebP - изображения карточки
- Трекер SQL-запросов: счётчик запросов участка кода и поиск лишних - чем считают запросы
- Карточка товара: вывод свойств, картинок и характеристик - что вообще выводится в карточке
- Производительность и кэш - устройство оптимизации целиком
- Объединение CSS и JS: включение, исключения, поломки - штатная склейка статики и её цена