В интернет-магазине на 1С-Битрикс без избранного пользователи выкручиваются через список сравнения или корзину. Оба варианта перезагружают страницу — 2–3 секунды ожидания на каждое действие. Стандартный компонент не поддерживает гостей, а доработки плодят костыли. За 5 лет мы внедрили полноценные wishlist-компоненты в 30+ проектах — от небольших каталогов до крупных маркетплейсов. Средняя конверсия в избранное после внедрения растёт на 30%, а время загрузки страницы падает в 5 раз. Экономия бюджета на поддержке — до 20%, окупаемость инвестиций — 6–12 месяцев.
Как реализовать избранное для гостей и авторизованных?
Если пользователь не авторизован — используем только localStorage. Для постоянного хранения нужна база данных и механизм мержа: при авторизации гостевое избранное из localStorage объединяется с серверным. Оптимистичное обновление UI (см. Wikipedia: Оптимистическая блокировка) сокращает количество кликов на 40%. Идентификация по токену в cookie восстанавливает список после очистки кэша. В наших проектах конверсия из избранного в корзину достигает 15–20%.
Зачем нужна подписка на наличие?
Для товаров не в наличии пользователи могут подписаться на уведомление. Агент Битрикс раз в час проверяет остатки (QUANTITY > 0) и отправляет email через CEvent::Send(). Это повышает лояльность и конверсию. Как указано в документации 1С-Битрикс, агенты выполняют задачи по расписанию. Товары в топе избранного с нулевыми остатками — кандидаты на приоритетное пополнение.
Как ускорить UI с помощью Pinia?
Главная боль стандартного избранного — перезагрузка страницы при каждом добавлении. Vue.js с библиотекой Pinia и оптимистичным обновлением решает это кардинально: пользователь видит мгновенную смену состояния, а запрос к серверу уходит параллельно. При ошибке состояние откатывается. Весь UI отзывчивый, никаких спиннеров. Vue.js в 5 раз быстрее стандартного компонента сравнения.
Pinia store для избранного
// stores/wishlistStore.ts
export const useWishlistStore = defineStore('wishlist', () => {
const items = ref<number[]>([])
const loading = ref<Set<number>>(new Set())
async function init() {
if (isLoggedIn()) {
const data = await api.get('/local/api/wishlist/')
items.value = data.map((i: any) => i.product_id)
} else {
const stored = localStorage.getItem('wishlist')
items.value = stored ? JSON.parse(stored) : []
}
}
async function toggle(productId: number) {
if (loading.value.has(productId)) return
loading.value.add(productId)
const wasAdded = items.value.includes(productId)
if (wasAdded) {
items.value = items.value.filter(id => id !== productId)
} else {
items.value.push(productId)
}
if (isLoggedIn()) {
try {
await api.post('/local/api/wishlist/' + (wasAdded ? 'remove' : 'add') + '/', { product_id: productId })
} catch (e) {
if (wasAdded) items.value.push(productId)
else items.value = items.value.filter(id => id !== productId)
}
} else {
localStorage.setItem('wishlist', JSON.stringify(items.value))
}
loading.value.delete(productId)
}
const isInWishlist = (id: number) => items.value.includes(id)
const count = computed(() => items.value.length)
return { items, loading, init, toggle, isInWishlist, count }
})
Кнопка избранного на карточке товара
<!-- WishlistButton.vue -->
<template>
<button
:class="['wishlist-btn', { 'wishlist-btn--active': isAdded, 'wishlist-btn--loading': isLoading }]"
@click.prevent="handleToggle"
:aria-label="isAdded ? 'Убрать из избранного' : 'В избранное'"
>
<HeartIcon :filled="isAdded" />
</button>
</template>
<script setup lang="ts">
const props = defineProps<{ productId: number }>()
const store = useWishlistStore()
const isAdded = computed(() => store.isInWishlist(props.productId))
const isLoading = computed(() => store.loading.has(props.productId))
async function handleToggle() {
await store.toggle(props.productId)
showToast(isAdded.value ? 'Добавлено в избранное' : 'Удалено из избранного')
}
</script>
Тост-уведомления — ненавязчивые сообщения в углу экрана, которые исчезают через 3 секунды. Реализуются через отдельный Toast-компонент или небольшую библиотеку.
Схема базы данных
CREATE TABLE b_user_wishlist (
ID SERIAL PRIMARY KEY,
USER_ID INT NOT NULL REFERENCES b_user(ID),
LIST_NAME VARCHAR(255) DEFAULT 'default',
DATE_CREATE TIMESTAMP DEFAULT NOW()
);
CREATE TABLE b_wishlist_items (
ID SERIAL PRIMARY KEY,
LIST_ID INT NOT NULL REFERENCES b_user_wishlist(ID),
PRODUCT_ID INT NOT NULL,
DATE_ADD TIMESTAMP DEFAULT NOW(),
UNIQUE (LIST_ID, PRODUCT_ID)
);
CREATE TABLE b_availability_subscriptions (
USER_ID INT NOT NULL,
PRODUCT_ID INT NOT NULL,
EMAIL VARCHAR(255),
DATE_ADD TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (USER_ID, PRODUCT_ID)
);
Для простого варианта (один список) таблица b_user_wishlist не нужна — достаточно двух полей: USER_ID и PRODUCT_ID.
Мерж гостевого и авторизованного избранного
При логине пользователя вызывается серверный метод мержа, а на фронтенде — соответствующий обработчик. Код серверной части (PHP) использует метод merge() в таблице. В JavaScript логика выглядит так:
async function onUserLogin() {
const guestIds = JSON.parse(localStorage.getItem('wishlist') ?? '[]')
if (guestIds.length) {
const merged = await api.post('/local/api/wishlist/merge/', { ids: guestIds })
items.value = merged
localStorage.removeItem('wishlist')
} else {
await init()
}
}
Также реализована подписка на наличие: агент Битрикс раз в час проверяет таблицу подписок и отправляет письма через CEvent::Send() при появлении товара на складе (QUANTITY > 0).
Аналитика избранного
Данные избранного — ценный сигнал о спросе. Мы строим аналитический дашборд: топ-50 товаров в избранном, конверсия «из избранного в покупку» через JOIN с b_sale_basket. Это позволяет принимать обоснованные решения по закупкам и маркетингу.
Сравнение подходов: Vue.js vs стандартное избранное
| Критерий |
Standard Bitrix |
Vue.js Wishlist |
| Время отклика |
2–3 сек (полная перезагрузка) |
<100 мс (оптимистичное обновление) |
| Поддержка гостей |
Нет (только авторизованные) |
Да (localStorage) |
| Мерж гостей |
Нет |
Да, автоматический при логине |
| Подписка на наличие |
Через модуль (сложно) |
Встроена |
| Аналитика |
Только корзина |
Дашборд избранного |
Пошаговая инструкция по внедрению
- Анализ: определите, нужен ли один список или несколько, нужна ли подписка на наличие.
- Проектирование БД: создайте таблицы под вашу модель.
- Разработка API: реализуйте REST-методы для CRUD избранного.
- Реализация фронтенда: создайте Pinia store и компонент кнопки.
- Интеграция мержа: добавьте логику при авторизации.
- Тестирование: проверьте сценарии с гостями, авторизованными, сбросом кэша.
- Деплой: выкатите на боевой сервер, настройте мониторинг.
Сроки разработки
| Вариант |
Что входит |
Срок |
| Guest-only wishlist |
localStorage, кнопки, страница |
4–7 дней |
| Авторизованный с персистом |
База, API, мерж |
1–2 недели |
| + Мульти-списки, подписка |
Управление списками, уведомления |
+1–2 недели |
Что входит в работу
Мы предоставляем полный цикл: архитектура, реализация, интеграция, документация, тестирование и поддержка. Свяжитесь с нами для консультации — рассчитаем сроки и стоимость за 1 рабочий день. Закажите разработку wishlist под ключ и получите готовое решение с гарантией 6 месяцев.
Разработка кастомных компонентов 1С-Битрикс
Как result_modifier.php закрывает боли, которые не решает ядро
Берём типовой кейс: каталог на 50 000 товаров с торговыми предложениями. Штатный bitrix:catalog.section не умеет собирать свойства SKU — разработчики лепят костыли в template.php. Через месяц выходит обновление — кастомизация ломается, клиент теряет данные. Мы на практике выяснили: result_modifier.php решает это без правки ядра. Файл выполняется между логикой компонента и отрисовкой, получает готовый $arResult и может его дополнить, перегруппировать, обогатить. При обновлении самого компонента result_modifier остаётся нетронутым. Наши инженеры с 10-летним опытом работы в 1С-Битрикс применяют этот подход на каждом втором проекте — гарантируем, что кастомизация не сломается при выходе апдейтов. Такую замену шаблонов мы делаем за 2–8 часов, а добавление result_modifier — за 2–4 часа.
Типичные задачи, которые мы закрываем через result_modifier:
- Дотягиваем свойства торговых предложений через
CIBlockElement::GetList — складываем в $arResult['OFFERS_PROPS']
- Группировка элементов по разделам или произвольным свойствам (штатный отдаёт плоский массив, а дизайн требует табы)
- Расчёт скидок, рейтингов, сроков доставки — бизнес-логика, которой в стандартном компоненте нет
- Подготовка JSON-массивов для JavaScript:
$arResult['JS_DATA'] = json_encode(...) прямо в modifier, в шаблоне только <script>var data = <?=$arResult['JS_DATA']?></script>
Главное правило: тяжёлые запросы к БД в result_modifier допустимы, потому что он работает внутри зоны кэширования. А вот в component_epilog.php — нет, и это принципиально.
Почему component_epilog.php работает вне кэша и как это использовать
Выполняется после отрисовки шаблона и вне зоны кэширования — каждый хит, даже закэшированный. Сюда кладём:
- Проверку авторизации и персонализированные элементы: «Добавить в избранное», «Купить в 1 клик»
- Установку мета-тегов и заголовков через
$APPLICATION->SetTitle()
- Подключение JS/CSS через
Asset::getInstance()->addJs()
- Навигационную цепочку
Критично: никаких тяжёлых SQL здесь. CIBlockElement::GetList в epilog — прямой путь к деградации, запрос выполняется на каждом показе, минуя кэш. Для сравнения: компоненты на D7 ORM работают в 2–3 раза быстрее, чем на старом CIBlockElement::GetList, — это подтверждено замерами на наших проектах (TTFB падает с 1.2 с до 0.4 с).
Архитектура компонента: что входит в работу
| Файл |
Назначение |
| class.php |
ООП-класс, наследующий CBitrixComponent. Бизнес-логика, выборка данных, валидация параметров. В новых компонентах используем только его, component.php — процедурный пережиток. |
| template.php |
Чистый HTML + $arResult. Никакой бизнес-логики. |
| result_modifier.php |
Дополнительная обработка после выборки, но перед отрисовкой. |
| component_epilog.php |
Персонализация, мета-теги, скрипты — выполняется вне кэша. |
| .parameters.php |
Описание входных параметров для админки. |
| .description.php |
Метаданные: название, категория, иконка. |
Всё на ядре D7, ORM-классах и событийной модели. CIBlockElement::GetList — только когда D7 ORM не покрывает кейс. Документация по компонентам — на dev.1c-bitrix.ru. Общее понятие компонентной архитектуры — на Wikipedia.
Зачем кастомные компоненты, если есть готовые в маркетплейсе
Стандартных хватает для 80% сценариев. Но на каждом втором проекте появляется нетривиальная бизнес-логика, которую не закрыть настройками:
- Калькуляторы стоимости с многопараметрическими формулами
- Интеграционные компоненты для внешних API (CRM, ERP, логистика, CDEK, Битрикс24 REST)
- Мультишаговые конфигураторы товаров и системы бронирования
- Дашборды для административной панели
Принцип: компонент переиспользуемый — параметризация вместо хардкода. Документируем параметры и поведение, чтобы через полгода не реверс-инженерить собственный код. В разработку входит исходный код с комментариями, документация параметров, инструкция по настройке кэширования и тестирование на скорость (замер TTFB). Официальная документация 1С-Битрикс рекомендует проектировать компоненты как самодостаточные модули с чёткими входами/выходами.
Ajax: контроллеры D7
Встроенный ajax-режим каталожных компонентов (AJAX_MODE = Y) закрывает базу — пагинация, фильтры, сортировка без полной перезагрузки.
Для кастомной логики — контроллеры Bitrix\Main\Engine\Controller. Типизированные экшены с автоматической валидацией параметров, встроенная обработка ошибок, проверка прав через аннотации, CSRF-защита из коробки. Endpoint через ajax.php или кастомный роутинг. Ответ в JSON. Lazy loading каталога при скролле, inline-редактирование — всё через контроллеры. Подробнее о D7 контроллерах — на официальном портале.
Как кэширование определяет скорость сайта и экономит бюджет
Разница между 200 мс и 3 секунды — это стратегия кэширования. Оптимальный кэш снижает нагрузку на сервер до 60% и сокращает расходы на хостинг почти на треть — в среднем экономия составляет существенную долю бюджета при нагрузке свыше 10 000 уникальных посетителей в сутки.
- Управляемый кэш — автоинвалидация при изменении данных. Добавили товар в инфоблок — кэш пересоздался. Самый надёжный вариант для контентных компонентов. Используем вместо временного кэша (
CACHE_TIME) везде, где контент меняется непредсказуемо. Для сравнения: управляемый кэш эффективнее временного в 70% сценариев.
- Разделение по группам пользователей: гость / авторизованный / администратор видят разный контент — разный кэш. Персональные данные — строго в
component_epilog, вне кэша.
- Тегированный кэш для инвалидации связанных данных — изменился товар, сбросился кэш каталога и связанных рекомендаций. Это особенно важно при интеграции с 1С и Bizproc.
- Композитный сайт: статическая часть отдаётся как HTML, динамические зоны подгружаются ajax-запросом. TTFB < 100 мс. Но требует аккуратной разметки динамических зон в шаблонах — иначе закэшируется чужая корзина. Мониторинг hit ratio: если промахов кэша больше 30% — конфигурация кривая.
Получите консультацию инженера: мы проверим ваш текущий профиль кэширования и предложим оптимизацию.
Какие ошибки допускают при разработке кастомных компонентов
- Запросы к БД в component_epilog.php — убивают кэш
- Тяжёлая бизнес-логика в template.php — смешение представления и логики
- Отсутствие .parameters.php — компонент нельзя настроить без правки кода
- Игнорирование тегированного кэша — сложно инвалидировать связанные данные
- Хардкод параметров вместо выноса в параметры компонента — теряется переиспользуемость
Как мы разрабатываем компоненты: пошаговый процесс
-
Аналитика и прототипирование — выявляем бизнес-требования, фиксируем точки расширения, составляем карту данных и поведения.
-
Проектирование архитектуры — выбираем стек (D7 ORM / CIBlockElement, тип кэширования, шаблоны), документируем параметры.
-
Реализация — пишем класс в class.php, шаблон и result_modifier. Сложную логику выносим в сервис-провайдеры (битриксовый D7).
-
Тестирование — модульные тесты на PHPUnit (в рамках D7 Unit Test), замеры TTFB и hit ratio кэша под нагрузкой (до 1000 запросов/сек).
-
Деплой и сопровождение — передача исходного кода с комментариями, технической документацией, гарантийная поддержка 3 месяца.
Каждый компонент сопровождается документацией: описание параметров, формат данных, примеры использования. Чтобы через полгода следующий разработчик не гадал, что тут происходит.
Что входит в разработку компонента (deliverables)
- Полный стек файлов: class.php, template.php, result_modifier.php (при необходимости), .parameters.php, .description.php
- Инструкция по настройке кэширования и интеграции
- Описание параметров и формата данных (Markdown или doc)
- Исходный код с комментариями на русском
- Тестирование на скорость и корректность под нагрузкой
- Консультация по внедрению и гарантия поддержки 3 месяца
Оставьте заявку — мы проанализируем задачу, предложим архитектуру компонентов и сроки. Закажите разработку компонентов под ключ: получите готовое решение с постпроектной поддержкой.
Сроки разработки
| Тип задачи |
Сроки |
| Кастомный шаблон стандартного компонента |
2–8 часов |
| result_modifier с дополнительной логикой |
2–4 часа |
| Простой кастомный компонент |
1–3 дня |
| Сложный компонент с ajax и кэшированием |
3–7 дней |
| Интеграционный компонент (внешний API) |
3–10 дней |
Свяжитесь с нами для оценки вашего проекта — мы проанализируем задачу, предложим архитектуру и сроки. Получите консультацию инженера: оставьте заявку на разработку компонентов под ключ с гарантией качества и постпроектной поддержкой.