Цель статьи — дать рабочий подход: после прочтения вы сможете добавить динамический фронтенд на Битрикс (динамические формы, фильтры каталога, корзина, личный кабинет) на реальном проекте — без магии и за рамками hello world.

1. Зачем использовать Vue.js в проектах на Битрикс?
Ключевые плюсы:
- SPA-опыт там, где это уместно. Фильтры каталога, корзина, личный кабинет — всё реагирует мгновенно, без перезагрузок. UX растёт, конверсия часто — тоже.
- Отделение логики от шаблонов. В Битриксе бизнес-логика часто «просачивается» в
template.php. Vue помогает держать состояние и UI-логику в компонентах, а сервер — в компонентах/контроллерах Bitrix. - Предсказуемость и тестируемость. Состояние — в одном месте (Composition API), данные приходят из понятных точек (AJAX/REST/
runComponentAction). - Инкрементальное внедрение. Не обязательно переписывать сайт в SPA. Можно встраивать маленькие островки Vue в существующие страницы («интеграция Vue с Битрикс» без революции).
2. Особенности архитектуры Битрикс: куда встраивать Vue
Где чаще всего живут «островки» Vue.js в Битрикс:
- Публичная часть
- В шаблоне сайта (
/local/templates/<site>/header.php,footer.php) — подключение ассетов. - В шаблонах стандартных компонентов (
/local/components/.../templates/.default/template.php) — создаём корневой контейнер и монтируем приложение.
- В шаблоне сайта (
- Компоненты
- Сервер подготавливает данные (
arResult) и рендерит базовую разметку. - Vue берёт готовые данные и дальше управляет UI/состоянием.
- Сервер подготавливает данные (
- AJAX-обработчики
- Современный путь — методы компонентов в режиме
Controllerable+BX.ajax.runComponentAction. - Либо контроллеры
\Bitrix\Main\Engine\ControllerсrunAction.
- Современный путь — методы компонентов в режиме
- Админка
- Кастомные страницы и настройки модуля, мини-SPA в админ-интерфейсе. (Подходы те же, только про SEO думать меньше.)
3. Подготовка среды: подключение Vue и сборка ассетов
Вариант A. Подключение через CDN (быстрый старт)
В шаблоне сайта (например, header.php) подключаем Vue 3:
<?php
use Bitrix\Main\Page\Asset;
Asset::getInstance()->addString('<script src="CDN_URL_TO_VUE3" defer></script>');
// Пример: прод-версия Vue 3 с ESM или IIFE — на ваше усмотрение.
// В проде лучше зафиксировать версию.
Плюсы: просто, быстро. Минусы: контроль и кеширование хуже, HMR в разработке нет.
Вариант B. Сборка через Vite/Webpack (рекомендуется для проекта)
Структура (пример):
/local/assets/vue-app/ # результат сборки (prod)
/local/assets/vue-app/manifest.json
/local/js/vue-src/ # исходники (dev)
main.ts
components/
...
Помощник для подключения манифеста Vite (prod) и HMR (dev):
<?php
function includeVite(string $entry = 'src/main.ts'): void
{
$isDev = getenv('VITE_DEV') === '1';
if ($isDev) {
echo '<script type="module" src="http://localhost:5173/@vite/client"></script>';
echo '<script type="module" src="http://localhost:5173/' . htmlspecialchars($entry) . '"></script>';
return;
}
$manifestPath = $_SERVER['DOCUMENT_ROOT'] . '/local/assets/vue-app/manifest.json';
if (!is_file($manifestPath)) return;
$manifest = json_decode(file_get_contents($manifestPath), true);
$item = $manifest[$entry] ?? null;
if (!$item) return;
foreach (($item['css'] ?? []) as $css) {
echo '<link rel="stylesheet" href="/local/assets/vue-app/' . htmlspecialchars($css) . '">';
}
echo '<script type="module" src="/local/assets/vue-app/' . htmlspecialchars($item['file']) . '"></script>';
}
В header.php достаточно сделать includeVite();.
Так мы получаем HMR в dev и минифицированные ассеты с хешами в prod — «правильный» динамический фронтенд на Битрикс.
4. Передача данных из Битрикс во Vue
Четыре рабочих способа, которые легко тестировать:
- Глобальная переменная (быстро и понятно)
<?php use Bitrix\Main\Web\Json; ?> <script> window.__APP_PROPS__ = <?= Json::encode($arResult['JS_DATA'] ?? []) ?>; </script> data-*атрибуты у корневогоdiv(удобно, если несколько инстансов на странице)<div id="vue-filter" data-props='<?= htmlspecialcharsbx(Json::encode($arResult['JS_DATA'] ?? [])) ?>'></div>- REST/контроллер — Vue сам запрашивает данные после монтирования (меньше SSR-данных).
- AJAX-компоненты —
BX.ajax.runComponentActionк вашему компоненту.
Дополнительно: строки переводов удобно передавать через BX.message().
5. Пример интеграции: динамический фильтр товаров внутри шаблона компонента
Разберём минимально жизнеспособный пример «островка» Vue — фильтр каталога с серверной реализацией на компоненте.
Файловая структура
/local/components/my/catalog.filter/
.description.php
class.php
/templates/.default/
template.php
result_modifier.php
class.php (серверная логика + AJAX-действие)
<?php
use Bitrix\Main\Engine\Contract\Controllerable;
use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\Loader;
use Bitrix\Main\Context;
class MyCatalogFilterComponent extends CBitrixComponent implements Controllerable
{
public function configureActions(): array
{
return [
'apply' => [
'prefilters' => [
new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),
new ActionFilter\Csrf(),
],
'postfilters' => [],
],
];
}
public function applyAction(array $filters = []): array
{
// Валидация входящих данных
$priceFrom = isset($filters['priceFrom']) ? (int)$filters['priceFrom'] : null;
$priceTo = isset($filters['priceTo']) ? (int)$filters['priceTo'] : null;
Loader::includeModule('iblock');
// Пример: выборка из ИБ (упрощённо)
$arFilter = ['IBLOCK_ID' => (int)$this->arParams['IBLOCK_ID'], 'ACTIVE' => 'Y'];
if ($priceFrom !== null) { $arFilter['>=PROPERTY_PRICE'] = $priceFrom; }
if ($priceTo !== null) { $arFilter['<=PROPERTY_PRICE'] = $priceTo; }
$items = [];
$res = \CIBlockElement::GetList(['SORT' => 'ASC'], $arFilter, false, ['nTopCount' => 20], ['ID','NAME','DETAIL_PAGE_URL','PROPERTY_PRICE']);
while ($el = $res->GetNext()) {
$items[] = [
'id' => (int)$el['ID'],
'name' => $el['~NAME'],
'url' => $el['~DETAIL_PAGE_URL'],
'price' => (int)$el['PROPERTY_PRICE_VALUE'],
];
}
return ['items' => $items];
}
public function executeComponent()
{
$this->arResult['JS_DATA'] = [
'iblockId' => (int)$this->arParams['IBLOCK_ID'],
'initial' => ['priceFrom' => null, 'priceTo' => null],
];
$this->includeComponentTemplate();
}
}
Обратите внимание: компонент реализует Controllerable, поэтому на фронте используем BX.ajax.runComponentAction.
result_modifier.php (готовим данные к фронту)
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
use Bitrix\Main\Web\Json;
// Можно дополнительно нормализовать данные
$arResult['JS_DATA'] = $arResult['JS_DATA'] ?? [];
template.php (корневой контейнер + монтирование Vue)
<?php if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
use Bitrix\Main\Web\Json;
$this->setFrameMode(true); // дружим с композитным режимом
// Обёртка динамической области (если включён композит)
// $frame = new \Bitrix\Main\Page\FrameHelper('vue_filter');
// $frame->begin();
?>
<div id="vue-filter"
data-props='<?= htmlspecialcharsbx(Json::encode($arResult["JS_DATA"])) ?>'></div>
<?php
// Подключаем ассеты Vue (см. раздел про Vite/CDN)
includeVite('src/main.ts'); // или Asset::getInstance()->addString(...) для CDN
// $frame->end();
?>
<script>
// Если вы подключаете Vue из CDN IIFE — получите глобал `Vue`.
// Если через Vite/Webpack ESM — импорт будет в /src/main.ts.
</script>
src/main.ts (Vue 3, Composition API)
import { createApp, ref, onMounted } from 'vue';
function parseProps(el: HTMLElement) {
try {
return JSON.parse(el.dataset.props || '{}');
} catch {
return {};
}
}
const App = {
name: 'CatalogFilter',
setup() {
const root = document.getElementById('vue-filter') as HTMLElement;
const props = parseProps(root);
const priceFrom = ref<number|null>(props.initial?.priceFrom ?? null);
const priceTo = ref<number|null>(props.initial?.priceTo ?? null);
const loading = ref(false);
const items = ref<any[]>([]);
const error = ref<string|null>(null);
const apply = async () => {
loading.value = true;
error.value = null;
try {
const response = await BX.ajax.runComponentAction(
'my:catalog.filter',
'apply',
{
mode: 'class',
data: {
filters: { priceFrom: priceFrom.value, priceTo: priceTo.value }
}
}
);
items.value = response.data.items;
} catch (e: any) {
error.value = 'Ошибка загрузки';
} finally {
loading.value = false;
}
};
onMounted(() => {
apply(); // загрузка начальных данных
});
return { priceFrom, priceTo, loading, items, error, apply };
},
template: `
<div class="b-filter">
<div class="b-filter__row">
<label>Цена от: <input type="number" v-model.number="priceFrom" min="0"></label>
<label>до: <input type="number" v-model.number="priceTo" min="0"></label>
<button :disabled="loading" @click="apply">Применить</button>
</div>
<div v-if="error" class="b-filter__error">{{ error }}</div>
<div v-if="loading">Загрузка…</div>
<ul class="b-filter__list" v-if="!loading && items.length">
<li v-for="it in items" :key="it.id">
<a :href="it.url">{{ it.name }}</a> — {{ it.price }} ₽
</li>
</ul>
<div v-if="!loading && !items.length">Ничего не найдено</div>
</div>
`
};
const app = createApp(App);
app.mount('#vue-filter');
Готово: это интеграция Vue с Битрикс в реальном компоненте. Сервер решает бизнес-логику и безопасность, фронт — UX.
6. Работа с формами и AJAX: отправка данных во входные точки Битрикс
Вариант 1. BX.ajax.runComponentAction (как в примере выше)
- Автоматически подставляет
sessid. - Ясная маршрутизация:
vendor:component, метод,mode: 'class'.
Вариант 2. Контроллеры \Bitrix\Main\Engine\Controller
PHP (контроллер модуля/проекта):
<?php
namespace My\Project\Controller;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\ActionFilter;
class Forms extends Controller
{
public function getDefaultPreFilters(): array
{
return [
new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),
new ActionFilter\Csrf(),
];
}
public function feedbackAction(array $payload): array
{
// Валидация, сохранение, почтовое событие и т.д.
return ['ok' => true];
}
}
Vue (fetch напрямую):
const send = async (payload: any) => {
const res = await fetch('/bitrix/services/main/ajax.php?action=my:project.Forms.feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payload, sessid: BX.bitrix_sessid() }),
credentials: 'same-origin',
});
const data = await res.json();
if (!data || data.status !== 'success') throw new Error('Request failed');
return data;
};
Советы по формам
- Валидируйте на фронте и на бэке. На фронте — UX, на бэке — безопасность.
- CSRF: используйте
ActionFilter\Csrf()иBX.bitrix_sessid(). - Ошибки: возвращайте осмысленные коды/сообщения; на фронте показывайте пользователю понятные тексты.
- Файлы: для загрузок используйте
FormData, не забудьте про ограничения PHP/Битрикс и проверку MIME.
7. Советы и подводные камни
Кеширование и композит:
- В шаблонах включайте
<?php $this->setFrameMode(true); ?>. - Если страница в композитном режиме, заверните Vue-островок в динамическую область (FrameHelper) либо инициализируйте Vue после получения динамических данных.
- Для блоков, которые часто меняются (корзина, лайки), не кладите динамику в жёсткий кеш компонента; разделяйте: «скелет» — кешируется, «данные» — AJAX.
SEO (публичная часть):
- Если критичный контент (названия товаров, цены) нужен роботу — рендерите базовую разметку сервером, а Vue улучшает интерактивность (прогрессивное улучшение).
- Для фильтров думайте о чистых URL и серверной выдаче по ним; Vue — для UX, а не вместо серверной страницы.
- Добавьте
noscriptс базовым контентом, если уместно.
Совместимость с Bitrix API и JS-ядром:
- Не ломайте BX-события. Если компонент Битрикса что-то подгружает через AJAX, Vue-инстанс может отвалиться. Решение — слушать события
BXи пере-монтировать при динамической подгрузке (или использовать MutationObserver). - Глобальные имена. Избегайте коллизий в
window; используйте неймспейсы, модули. - Локализация.
BX.message()отлично подходит для строк, которыми питается Vue.
Инициализация на динамическом контенте:
- Если часть DOM прилетает позже (например, пагинация сетки), подписывайтесь на события (например, грид может эмитить кастомные события) и повторно монтируйте/обновляйте Vue-компонент.
- Перед повторным монтированием вызывайте
app.unmount()у предыдущего инстанса, чтобы избежать утечек.
Производительность:
- Дробите приложение на мини-виджеты вместо одного большого.
- Используйте ленивую загрузку компонентов (dynamic import) в сборке.
- Следите за количеством реактивных источников; вычисляемые значения (
computed) вместо «ручных» вотчеров.
8. Когда Vue — оправданное решение, а когда — избыточное?
Оправдано:
- Динамический фильтр каталога, быстрый поиск/сортировка без релоуда.
- Корзина/мини-корзина, сравнение товаров, избранное.
- Кабинет пользователя: заказы, адреса, формы, статусы, история — всё живёт в состоянии.
- Сложные формы (много шагов, валидация, зависимые поля).
Избыточно:
- Простые статические блоки, где нужна только пара «показать/скрыть».
- Одноразовые лендинги без интерактива.
- Страницы, критичные для SEO и полностью сервер-рендерные (там лучше сначала навести порядок в шаблонах компонентов).
Правило простое: если есть состояние и много пользовательских действий — Vue окупится. Если нет — сделайте минимальный JS или используйте стандартные возможности компонентов.
Заключение
Интеграция Vue.js в Битрикс — это не «или-или», а инкрементальная модернизация. Начните с маленького «островка» (фильтр, форма, мини-корзина), наладьте поток данных (передача arResult → Vue, AJAX-действия), приручите кеш/композит — и дальше масштабируйте. Такой подход даёт современный UX и сохраняет сильные стороны Битрикса: готовые модули, компоненты, админку и понятную серверную модель.
Если коротко — Vue.js в Битрикс отлично работает, если вы:
- держите серверную логику на стороне компонентов/контроллеров,
- отдаёте Vue чистые данные и
- уважаете кеш/SEO.
Тогда интеграция Vue с Битрикс превращается из эксперимента в стабильный производственный паттерн. Удачных релизов!