«Давайте перепишем сайт на Vue» — фраза, после которой у опытного разработчика 1С-Битрикс сводит скулы. Не потому, что Vue или React плохи, а потому, что за ней часто стоит подмена: магазин с исправно работающим серверным рендерингом собираются превратить в клиентский SPA — и заодно потерять SEO, скорость и половину преимуществ платформы. При этом обратная крайность не лучше: городить умный фильтр, корзину и конфигуратор на «голом» jQuery, склеивая состояние строками в DOM, — путь к неподдерживаемому коду. Истина, как обычно, посередине, и она вполне конкретна.
Эта статья — трезвый разбор, как совместить сильные стороны серверного Битрикса с современным фронтендом. Разберём, где Vue и React реально оправданы (интерактивные части: фильтр, корзина, конфигуратор, личный кабинет, дашборд), а где они избыточны (простые контентные страницы — не надо). Покажем, как подключать точечные виджеты в контейнеры внутри штатных компонентов, как отдавать им данные из $arResult и получать через ajax-действия D7 и REST, как настроить сборку на Webpack или Vite, что делать с CSRF-токеном, SSR и гидратацией, чтобы не обрушить SEO и Core Web Vitals. Материал — выжимка из наших проектов по кастомной разработке на 1С-Битрикс.
Коротко
- Битрикс — серверный движок: для каталога, карточек и контента серверный рендеринг быстрее и лучше для SEO, чем клиентский SPA.
- Vue и React оправданы точечно — на интерактивных участках: умный фильтр, корзина, конфигуратор, личный кабинет, внутренние дашборды.
- Правильная модель — виджеты в контейнерах: пустой div в шаблоне компонента, в него монтируется собранный бандл; остальное остаётся серверным.
- Данные — стартовые из
$arResult(в data-атрибут), изменяемые — через ajax-действия контроллеров D7 (внутри сайта) или REST (внешний клиент), с CSRF-токеном. - Сборка — Vite или Webpack через npm; исходники в репозитории, хешированные бандлы в папке шаблона.
- Простой интерактив (табы, аккордеоны, валидация) — оставляйте на vanilla JS; фреймворк тащат только ради настоящего состояния.
Зачем магазину на Битриксе современный фронтенд
Ожидания пользователей выросли: мгновенный отклик фильтра без перезагрузки, корзина, которая пересчитывается на лету, конфигуратор товара с живым превью, личный кабинет, где всё меняется без перезагрузки страниц. Это интерфейсы, к которым людей приучили маркетплейсы, и на классическом «отправил форму — перезагрузил страницу» их не собрать. Вот здесь современный фронтенд — Vue, React и их экосистема — становится не модой, а инструментом под конкретную задачу.
Но у Битрикса своя природа: это в первую очередь серверный движок, который силён именно в отдаче готового HTML, кэше, композите и интеграции с 1С. Ошибка — воспринимать «современный фронтенд» как замену этому. Правильнее — как дополнительный слой поверх сильной серверной основы, включаемый ровно там, где интерактивность оправдывает свою цену. Задача разработчика — провести границу: что остаётся серверным, а что достаётся фреймворку.
Именно об этой границе и пойдёт речь. Мы не будем агитировать ни за «всё на React», ни за «никаких фреймворков». Мы разберём инженерный вопрос: как встроить Vue или React в проект на Битриксе так, чтобы получить выгоду в интерактиве и не заплатить за неё SEO, скоростью и поддерживаемостью. Начнём с того, как вообще устроен рендеринг в Битриксе по умолчанию — без этого невозможно понять, куда встраивается новый слой.
Классический серверный рендеринг Битрикса
По умолчанию Битрикс рендерит страницы на сервере. Схема простая и проверенная: приходит запрос, ядро собирает страницу из шаблона сайта и компонентов, компоненты выбирают данные и отрисовывают HTML своими шаблонами (template.php), и браузер получает уже готовую разметку. Пользователь и поисковый робот видят контент сразу — ничего не нужно «дорисовывать» в браузере. Как устроены эти шаблоны и компоненты, мы подробно разбирали в материале про шаблоны и компоненты 1С-Битрикс.
Интерактив в этой модели традиционно добавляли двумя способами. Первый — обычные ссылки и формы с перезагрузкой страницы: нажали «применить фильтр» — сервер собрал новую страницу. Второй — точечный AJAX на jQuery или ванильном JavaScript: отправили запрос, получили кусок HTML или JSON, подставили в DOM. Для многих задач этого хватает и сегодня, и недооценивать серверную модель — ошибка.
Сильные стороны серверного рендеринга Битрикса конкретны:
- SEO из коробки. Контент в HTML сразу, робот индексирует его без JS-рендеринга — критично для коммерческих страниц.
- Скорость первого показа. Вместе с кэшем компонентов и композитом страница отдаётся почти мгновенно, ещё до тяжёлой логики.
- Простота и предсказуемость. Нет отдельного фронтенд-приложения, нет рассинхрона состояния клиента и сервера, ниже порог поддержки.
- Готовая интеграция. Данные, права, сессии, обмен с 1С — всё уже на сервере, не нужно строить API ради каждой мелочи.
Слабое место у этой модели одно, но существенное: сложный интерактив с богатым состоянием собирать вручную тяжело. Когда на странице десятки взаимозависимых элементов, которые меняются без перезагрузки, императивный код на jQuery превращается в клубок. Вот эту нишу и закрывают Vue и React — и только её.
Где серверного рендеринга достаточно
Прежде чем звать фреймворк, полезно честно перечислить, где он не нужен вовсе. На типичном магазине это большинство страниц. Их достаточно рендерить на сервере, и любые попытки «осовременить» их клиентским приложением дают минус к скорости и SEO без плюса к пользе.
- Контентные и информационные страницы. «О компании», доставка, оплата, статьи блога, лендинги. Здесь важен текст и его индексация, а интерактива почти нет.
- Списки и карточки каталога. Разделы, товары, детальные страницы. Это ядро SEO магазина — контент обязан быть в HTML сразу. Серверный рендер здесь не обсуждается.
- Простые формы. Обратная связь, заявка, подписка. Их закрывает штатная отправка плюс лёгкий AJAX без всякого фреймворка.
- Навигация и статические блоки. Меню, хлебные крошки, футер, баннеры. Никакой реактивности — обычные компоненты.
Общий признак «серверной» страницы — состояние либо отсутствует, либо тривиально (открыть/закрыть, показать/скрыть). Когда пользователь в основном читает и переходит по ссылкам, а не манипулирует сложным интерфейсом, серверный рендеринг оптимален по всем осям: скорость, SEO, простота, стоимость поддержки. Тащить сюда Vue или React — значит платить за то, чем не пользуешься.
Что добавляет Vue и React: слой интерактива
Ценность Vue и React — не в «модности», а в конкретной инженерной модели: декларативный UI, управляемый состоянием. Вы описываете, как интерфейс должен выглядеть при данном состоянии, а фреймворк сам обновляет DOM, когда состояние меняется. Больше не нужно вручную находить элементы и синхронизировать их — вы меняете данные, а представление подстраивается.
Именно это решает главную боль сложного интерактива на «голом» JS. Представьте умный фильтр: пользователь щёлкает чекбоксы, меняет диапазон цены, и одновременно должны обновиться список товаров, счётчик найденного, активные метки выбранных значений, доступность других чекбоксов, URL. На jQuery это десятки императивных обработчиков, которые тянут друг за друга и легко рассыпаются. На Vue или React это одно реактивное состояние, от которого автоматически зависит весь UI.
Что конкретно даёт этот слой:
- Реактивность. Изменили данные — интерфейс обновился сам, без ручной возни с DOM.
- Компонентность на клиенте. UI разбит на переиспользуемые компоненты с чёткими входами и событиями — код читаемый и тестируемый.
- Управляемое состояние. Единый источник правды вместо разбросанных по DOM значений; проще отлаживать и расширять.
- Экосистема. Готовые решения для маршрутизации внутри виджета, стора, форм, валидации, компонентных библиотек.
Ключевая мысль: этот слой ценен ровно там, где есть богатое изменяемое состояние. Нет состояния — нет и выгоды, только накладные расходы на бандл и сборку. Поэтому дальше — самый практический вопрос: на каких именно участках магазина состояние достаточно сложное, чтобы фреймворк окупился.
Где Vue и React реально оправданы
Есть узнаваемый набор участков интернет-магазина, где интерактивность настолько богата, что фреймворк окупается почти всегда. Это места, где пользователь активно манипулирует интерфейсом, а состояние меняется часто и взаимозависимо.
Умный фильтр каталога
Мгновенный пересчёт товаров при изменении условий, живой счётчик найденного, активные метки, синхронизация с URL, доступность значений в зависимости от выборки. Много взаимозависимого состояния — классический кандидат на Vue/React-виджет. При этом сам список товаров можно оставлять серверным и обновлять его через запрос, а фреймворк держит только панель фильтра и её логику.
Корзина и мини-корзина
Добавление и удаление без перезагрузки, изменение количества, пересчёт сумм и скидок, промокоды, синхронизация мини-корзины в шапке с состоянием на странице. Состояние живёт на нескольких участках страницы и должно быть согласованным — реактивная модель здесь заметно чище императивной.
Конфигуратор товара
Выбор опций, комплектаций, цвета, размера с живым пересчётом цены, доступности и превью. Взаимозависимые опции (одна ограничивает другие), динамическая цена — то место, где декларативный UI экономит массу кода и ошибок.
Личный кабинет и dashboard
Списки заказов с фильтрами, статусы, повтор заказа, редактирование профиля, внутренние панели для B2B-клиентов и менеджеров, аналитические дашборды. Это по сути мини-приложения внутри сайта, где SPA-подход внутри одного раздела уместен: контент за авторизацией, SEO не требуется, а интерактивность высока.
| Участок | Почему фреймворк оправдан | SEO-критичность |
|---|---|---|
| Умный фильтр | Много взаимозависимого состояния, живой пересчёт | Средняя (контент товаров — серверный) |
| Корзина / мини-корзина | Согласованное состояние на нескольких участках | Низкая (персональное) |
| Конфигуратор | Взаимозависимые опции, динамическая цена и превью | Средняя |
| Личный кабинет | Мини-приложение за авторизацией | Нет (закрыто от индексации) |
| Внутренний dashboard | Сложные таблицы, фильтры, живые данные | Нет |
Обратите внимание на колонку SEO: чем ниже требования к индексации, тем свободнее можно применять клиентский рендер. Личный кабинет и дашборд закрыты авторизацией — там фреймворк почти без ограничений. Фильтр и конфигуратор стоят на индексируемых страницах — там держим серверный контент, а виджет отвечает только за интерактив.
Где фреймворк избыточен
Зеркальная половина честности — назвать места, где Vue или React принесут больше вреда, чем пользы. Соблазн «раз уж подключили фреймворк, давайте везде» велик, но именно он рождает раздутые бандлы и медленные страницы.
- Простые контентные страницы. «О компании», доставка, статьи — тут нечего реактивно обновлять. Фреймворк только утяжелит.
- Статические списки и карточки без интерактива. Если карточка товара — это картинки, описание и кнопка «в корзину», её незачем рисовать на клиенте; кнопку оживит маленький виджет корзины, остальное серверное.
- Одиночные UI-элементы. Табы, аккордеон, модалка, слайдер, «показать ещё» — это vanilla JS на пару десятков строк, а не повод тащить рантайм фреймворка.
- Формы обычной сложности. Валидация и отправка формы заявки не требуют стора и реактивности; штатных средств и лёгкого JS достаточно.
Общее правило-антипод: если на участке нет разделяемого изменяемого состояния и сложной синхронизации UI, фреймворк избыточен. Он добавляет вес бандла, шаг сборки и когнитивную нагрузку, ничего не давая взамен. Хуже того — превращение индексируемого контента в клиентский рендер напрямую бьёт по SEO. Поэтому «где не надо» так же важно проектировать, как «где надо».
Таблица: нужен фреймворк или нет
Соберём критерий выбора в одну таблицу — по ней удобно принимать решение на этапе проектирования конкретного блока. Смотрите не на «модно/немодно», а на характер состояния и требования к SEO.
| Признак блока | Серверный рендер / vanilla | Vue / React-виджет |
|---|---|---|
| Состояние | Отсутствует или тривиальное | Богатое, изменяемое, взаимозависимое |
| Частота перерисовок | Редко, по действию | Часто, реактивно |
| Синхронизация UI | Один-два элемента | Много связанных элементов |
| SEO-важность контента | Высокая — нужен серверный HTML | Низкая или контент дублируется серверно |
| Примеры | Карточка, раздел, статья, форма, табы | Фильтр, корзина, конфигуратор, ЛК, dashboard |
| Стоимость поддержки | Ниже: нет сборки и рантайма | Выше: сборка, версии, компетенции |
Если признаки блока в основном в левой колонке — оставляйте серверный рендеринг, при необходимости добавив немного ванильного JS. Если преобладает правая — это кандидат на виджет. Пограничные случаи решайте в пользу простоты: дешевле потом «поднять» блок до фреймворка, чем годами тащить лишнюю сложность там, где она не окупилась.
Три способа подключить современный фронтенд
Технически интегрировать Vue или React с Битриксом можно на трёх уровнях глубины. Они отличаются степенью связанности с платформой и радикальностью перестройки. Для большинства проектов оптимален первый — самый лёгкий.
| Способ | Суть | Когда выбирать |
|---|---|---|
| Точечные виджеты | Фреймворк монтируется в контейнеры внутри штатных компонентов; страница остаётся серверной | Почти всегда: фильтр, корзина, конфигуратор на обычном сайте |
| SPA-раздел | Целый раздел (личный кабинет, B2B-панель) — клиентское приложение за авторизацией | Внутренние интерфейсы, где SEO не нужен, а интерактив высок |
| Headless | Фронтенд полностью отделён, Битрикс — только бэкенд и источник данных через API | Крайняя форма: сильная команда, готовность к SSR и своей инфраструктуре |
Разница принципиальна. Точечные виджеты сохраняют всё, чем силён Битрикс: серверный HTML, кэш, композит, SEO. Вы просто добавляете островки интерактива. SPA-раздел изолирует клиентское приложение в зоне, где индексация не нужна, — компромисс без риска для витрины. Headless же переворачивает всё: фронтенд становится самостоятельным приложением, а Битрикс — «головой без тела», отдающей данные. Это мощно, но дорого и требовательно; о нём — отдельный раздел ниже.
Дальше разберём первый способ детально, потому что он покрывает подавляющее большинство реальных задач магазина и при этом наименее рискован.
Точечные виджеты: монтируем в контейнер компонента
Идея точечного виджета проста: шаблон компонента Битрикса выводит обычный серверный HTML, а внутри него оставляет пустой контейнер — div с известным id или data-атрибутом. На клиенте собранный бандл находит этот контейнер и монтирует в него Vue- или React-приложение. Всё, что вне контейнера, остаётся серверным и продолжает работать как раньше.
В шаблоне компонента (например, в template.php кастомного каталога) это выглядит так:
<?php if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); ?>
<!-- Серверный HTML остаётся, виджет монтируется в пустой контейнер -->
<div class="catalog-layout">
<div id="smart-filter-app"
data-section="<?= htmlspecialcharsbx($arParams['SECTION_CODE']) ?>"></div>
<div class="catalog-list">
<?php // серверный список товаров — индексируется как обычно ?>
<?php foreach ($arResult['ITEMS'] as $item): ?>
<!-- карточка товара, отрисованная сервером -->
<?php endforeach; ?>
</div>
</div>
Скрипт виджета подключают штатно — через API ядра, а не тегом <script> в середине вёрстки, чтобы не ломать объединение ресурсов и композит:
<?php
// В template.php или result_modifier.php шаблона компонента
$this->addExternalJs($templateFolder . '/dist/smart-filter.[hash].js');
$this->addExternalCss($templateFolder . '/dist/smart-filter.[hash].css');
Сам бандл на Vue монтируется в контейнер, когда DOM готов. Точка входа виджета минимальна:
// src/smart-filter/main.js — точка входа виджета
import { createApp } from 'vue';
import SmartFilter from './SmartFilter.vue';
const el = document.getElementById('smart-filter-app');
if (el) {
createApp(SmartFilter, {
section: el.dataset.section,
}).mount(el);
}
Аналогично устроен React-виджет — вместо createApp().mount() вызывается createRoot(el).render(...). Ключевой принцип один: виджет знает только про свой контейнер и ничего не ломает вокруг. На странице может быть несколько независимых виджетов (фильтр, мини-корзина, конфигуратор) — каждый в своём контейнере, со своим бандлом, не мешая ни друг другу, ни серверному рендеру.
Данные во фронтенд: из $arResult
Виджету нужны данные. Первый и часто лучший способ передать их — прямо из $arResult, ведь компонент уже выбрал всё нужное на сервере. Если стартовое состояние известно на момент рендера страницы, не стоит заставлять браузер делать лишний запрос: отдайте данные сразу, чтобы виджет монтировался уже с наполнением. Это быстрее и не даёт «мигания» пустотой, что важно и для восприятия, и для метрик скорости.
Технически данные сериализуют в JSON и кладут в data-атрибут контейнера или в отдельный тег. Обязательно экранируйте вывод — это данные в HTML-атрибуте:
<?php
use Bitrix\Main\Web\Json;
// В result_modifier.php готовим компактный набор для виджета
$arResult['FILTER_JSON'] = Json::encode([
'sectionId' => (int)$arResult['SECTION']['ID'],
'properties' => $arResult['FILTER_PROPERTIES'], // диапазоны, значения
'selected' => $arResult['SELECTED_VALUES'],
]);
?>
<!-- в template.php -->
<div id="smart-filter-app"
data-state="<?= htmlspecialcharsbx($arResult['FILTER_JSON']) ?>"></div>
Во фронтенде виджет читает это состояние при монтировании:
const el = document.getElementById('smart-filter-app');
const initialState = JSON.parse(el.dataset.state || '{}');
createApp(SmartFilter, { initialState }).mount(el);
На практике модели комбинируют: стартовое состояние — из $arResult (быстро, без запроса, дружелюбно к SEO), а дальнейшие изменения — запросами (пользователь щёлкнул фильтр — виджет запросил пересчёт). Так вы получаете лучшее из двух миров: мгновенный первый показ с данными и живой интерактив дальше. Важно не переусердствовать с объёмом данных в атрибуте: кладите только то, что нужно виджету на старте, а не весь массив компонента, иначе раздуете HTML.
$arResult кэшируются вместе с компонентом — это хорошо для общих данных фильтра. Но персональное (содержимое корзины, имя пользователя) в кэш попадать не должно: его отдают в некэшируемой части или в динамической области композита. Иначе виджет корзины покажет всем данные одного пользователя.
Взаимодействие с бэкендом: ajax-действия D7
Как только пользователь начинает менять состояние — применяет фильтр, добавляет товар в корзину, листает страницы, — виджету нужно общаться с сервером. Внутри того же сайта самый короткий и правильный путь — ajax-действия контроллеров D7. Это встроенный механизм ядра: вы описываете класс-контроллер с методами-действиями, а фронтенд дёргает их по служебному адресу.
Контроллер наследуется от базового класса ядра и объявляет действия. Ядро само разберёт параметры, проверит сессию и CSRF, вернёт JSON:
<?php
// local/php_interface/lib/Controller/Catalog.php (упрощённо)
namespace Vendor\Shop\Controller;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\ActionFilter;
class Catalog extends Controller
{
// Разрешаем неавторизованным читать фильтр, но защищаем изменяющие действия
public function configureActions()
{
return [
'filter' => [
'prefilters' => [ new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]) ],
],
];
}
// Действие: пересчёт фильтра. Вернёт JSON автоматически
public function filterAction(int $sectionId, array $values): array
{
$result = \Vendor\Shop\Filter::apply($sectionId, $values);
return [
'count' => $result['COUNT'],
'items' => $result['ITEMS'], // или HTML списка
'facets' => $result['FACETS'], // доступные значения
];
}
}
Фронтенд вызывает действие по служебному эндпоинту ядра, передавая полное имя контроллера, имя действия и CSRF-токен:
async function applyFilter(sectionId, values, sessid) {
const body = new FormData();
body.append('sectionId', sectionId);
body.append('sessid', sessid); // CSRF-токен Битрикса
Object.entries(values).forEach(([k, v]) => body.append(`values[${k}]`, v));
const res = await fetch(
'/bitrix/services/main/ajax.php?action=vendor:shop.Catalog.filter',
{ method: 'POST', body, headers: { 'X-Requested-With': 'XMLHttpRequest' } }
);
const json = await res.json();
return json.data; // ядро оборачивает ответ в { status, data, errors }
}
Плюсы этого пути очевидны: не нужно поднимать отдельный API, авторизация уже работает через сессию пользователя, ядро само проверяет CSRF и оборачивает ответ в единый формат { status, data, errors }. Для виджетов на той же странице того же сайта это способ по умолчанию. Полноценный REST нужен в другой ситуации — о ней следующий раздел.
Взаимодействие через REST
Когда клиент внешний или обособленный — мобильное приложение, отдельный headless-фронтенд на своём домене, интеграция со сторонней системой — вместо ajax-действий используют полноценный REST. Разница принципиальна: ajax-действия завязаны на сессию того же сайта, а REST — самостоятельный интерфейс с собственной аутентификацией по токенам (OAuth), пригодный для любого клиента откуда угодно.
В экосистеме Битрикса REST реализуют либо через модуль REST и приложения (rest-приложения с OAuth-токенами), либо собственными эндпоинтами на базе того же D7 — маршрутами, которые отдают и принимают JSON и проверяют токен доступа. Для headless-витрины обычно строят продуманный слой API: каталог, разделы, товар, корзина, оформление заказа — каждый ресурс с чёткими методами. Мы подробно разбираем проектирование такого слоя в отдельном материале про REST API в 1С-Битрикс, а построение самого API под ключ — это наша услуга разработки REST API для 1С-Битрикс.
| Критерий | Ajax-действия D7 | REST |
|---|---|---|
| Кто клиент | Виджет на той же странице того же сайта | Внешний: приложение, headless, интеграция |
| Аутентификация | Сессия пользователя + CSRF (sessid) | Токены доступа (OAuth) |
| Порог входа | Низкий: контроллер + fetch | Выше: проектирование API, версии, документация |
| Формат | JSON, обёртка ядра { status, data, errors } | JSON, свой контракт ресурсов |
| Когда выбирать | По умолчанию для виджетов сайта | Обособленный фронтенд, headless, мобильные |
Практический вывод: не поднимайте REST ради виджета на своей же странице — это лишняя работа и лишняя поверхность безопасности. Ajax-действия закрывают этот сценарий проще и надёжнее. REST резервируйте для случаев, когда клиент действительно внешний и сессии сайта ему недоступны. Выбор правильного механизма взаимодействия экономит недели и снижает риски.
CSRF-токен и безопасность запросов
Любой изменяющий запрос к Битриксу должен нести CSRF-токен — это защита от подделки запросов. В Битриксе токен называется bitrix_sessid, и без него ajax-действия, меняющие данные, отклоняются. Игнорировать это нельзя: и потому что не заработает, и потому что это реальная мера безопасности.
Токен генерирует сервер, и его нужно передать во фронтенд. Правильный способ — вывести его на клиент так, чтобы он был свежим на каждый запрос (то есть не в кэшируемой части шаблона, а в некэшируемой области или в подвале):
<?php // в подвале или в некэшируемой области — токен персональный ?>
<script>
window.SHOP_SESSID = '<?= htmlspecialcharsbx(bitrix_sessid()) ?>';
</script>
Во фронтенде токен подставляют в каждый POST-запрос — параметром sessid и/или заголовком, вместе с X-Requested-With:
async function postAction(action, payload) {
const body = new FormData();
body.append('sessid', window.SHOP_SESSID);
Object.entries(payload).forEach(([k, v]) => body.append(k, v));
return fetch(`/bitrix/services/main/ajax.php?action=${action}`, {
method: 'POST',
body,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-Bitrix-Csrf-Token': window.SHOP_SESSID,
},
}).then(r => r.json());
}
Несколько правил безопасности, о которых легко забыть:
- Токен персональный и кэшу не подлежит. Если он попадёт в кэш компонента, всем отдастся один и тот же (и чужой) sessid — запросы сломаются или откроют дыру. Держите его в некэшируемой области.
- Проверяйте права на действии, а не только токен. CSRF-токен подтверждает, что запрос со страницы вашего сайта, но не заменяет проверку прав пользователя. В контроллере D7 добавляйте фильтры доступа.
- Экранируйте всё, что кладёте в HTML/JS. Токен, данные из
$arResult— черезhtmlspecialcharsbx(), чтобы не открыть XSS. - Для REST — токены доступа, а не sessid. Внешние клиенты аутентифицируются по OAuth-токену; sessid к ним не применим.
Сборка: Webpack, Vite и npm
Как только появляется Vue или React с однофайловыми компонентами и import, нужен шаг сборки: исходники превращаются в готовые бандлы, которые понимает браузер. Управляет зависимостями npm, а собирает Webpack или Vite. Для точечных виджетов задача, по сути, одна — собрать несколько независимых бандлов и положить их в папку шаблона.
Vite обычно приятнее в разработке: быстрый старт, мгновенный HMR (горячая замена модулей), лаконичная конфигурация и удобный library-режим для сборки отдельных виджетов. Webpack — зрелый и предсказуемый, с огромной экосистемой; он часто уже используется в проекте или в механике расширений самого ядра. Для наших задач разница не драматична — важнее дисциплина организации, а не выбор инструмента.
Минимальная сборка виджетов на Vite в режиме библиотеки может выглядеть так:
// vite.config.js — сборка независимых виджетов
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
outDir: 'local/templates/main/dist',
rollupOptions: {
input: {
'smart-filter': 'src/smart-filter/main.js',
'cart': 'src/cart/main.js',
'configurator':'src/configurator/main.js',
},
output: {
entryFileNames: '[name].[hash].js', // хеш для сброса кэша
assetFileNames: '[name].[hash].[ext]',
},
},
},
});
Скрипты в package.json дают команде понятные точки входа — разработку и продакшен-сборку:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"watch": "vite build --watch"
}
}
Практический критерий выбора прост: начинаете с нуля и собираете набор виджетов — берите Vite; встраиваетесь в существующий пайплайн или в механику расширений ядра — оставайтесь на том, что уже есть. Не тратьте недели на спор об инструменте: и Webpack, и Vite решают задачу. Гораздо важнее — как вы храните исходники и бандлы, и об этом дальше.
Где хранить исходники и бандлы
Организация файлов — то, что отличает поддерживаемый проект от «свалки». Правило простое: исходники и собранные бандлы — в репозитории, зависимости — нет. Разберём по пунктам.
- Исходники виджетов (
src/с*.vue,*.jsx,*.ts, стилями и конфигами сборки) держат в репозитории проекта. Удобно — в отдельной папке фронтенда рядом сpackage.json, внеbitrix/. - Собранные бандлы (
dist/) кладут в папку шаблона сайта — например,local/templates/main/dist/, — откуда их подключает Битрикс черезaddExternalJs. Многие команды коммитят и бандлы тоже, чтобы деплой не требовал сборки на проде. - node_modules — вне git. Зависимости ставятся командой
npm ciпоpackage-lock.json; хранить их в репозитории не нужно. - Хеш в имени бандла (
filter.[hash].js) сбрасывает кэш браузера при каждом обновлении. Это избавляет от вечной проблемы «у пользователя старая версия скрипта».
Где именно собирать — тоже вопрос дисциплины. Идеально — на этапе CI/CD: пуш в репозиторий запускает сборку, готовые бандлы попадают на сервер вместе с кодом. Тогда на боевом сервере не нужен Node.js, а версия фронтенда всегда соответствует версии бэкенда. Как выстроить такой конвейер, мы разбираем в связке с общей темой деплоя и окружений. Ключевое здесь — воспроизводимость: любой разработчик по npm ci && npm run build получает идентичный результат, а на прод едет ровно то, что собрано и проверено.
State-менеджмент: когда он нужен
Вокруг стейт-менеджеров (Pinia для Vue, Redux/Zustand для React) много шума, и новички тащат их в каждый виджет. На деле для большинства точечных виджетов отдельный стор не нужен: локального состояния компонента и передачи данных через входные параметры и события вполне достаточно. Фильтр, конфигуратор, небольшая форма прекрасно живут на встроенной реактивности без внешнего стора.
Стейт-менеджер начинает окупаться, когда состояние разделяется между несколькими независимыми виджетами или частями большого приложения. Классический пример на магазине — корзина: её состояние нужно и мини-корзине в шапке, и блоку «в корзине» на карточке, и странице оформления. Держать его в общем сторе логично — иначе придётся вручную синхронизировать разрозненные виджеты.
| Ситуация | Решение |
|---|---|
| Один изолированный виджет | Локальный стейт компонента, без стора |
| Данные из родителя в дочерний | Входные параметры (props) и события |
| Состояние на 2–3 связанных элемента | Небольшой общий объект/composable |
| Состояние делят независимые виджеты (корзина) | Стор: Pinia / Redux / Zustand |
| Большое SPA-приложение (личный кабинет) | Стор + маршрутизация внутри приложения |
Ориентир: вводите стор, когда реально появляется разделяемое состояние, а не заранее «на будущее». Преждевременный стейт-менеджер усложняет простой виджет и повышает порог входа для команды. Начинайте с локального состояния — и поднимайте до стора ровно тогда, когда синхронизация вручную становится болью. Это тот же принцип экономии сложности, что и с выбором самого фреймворка.
TypeScript: когда оправдан
TypeScript добавляет статическую типизацию поверх JavaScript — и вместе с ней шаг компиляции и определённый порог входа. Стоит ли он того на проекте Битрикса? Зависит от масштаба фронтенда и состава команды, и ответ не одинаков для всех.
TypeScript оправдан, когда фронтенд-часть большая и живёт долго: сложный личный кабинет, B2B-панель, дашборды, headless-витрина. Здесь типы окупаются — они ловят ошибки на этапе сборки, документируют контракты данных (особенно ответы API), заметно облегчают рефакторинг и передачу кода между разработчиками. Чем больше кодовая база и чем дольше её поддерживать, тем сильнее выгода.
TypeScript избыточен, когда виджетов немного и они небольшие: пара сотен строк на фильтр и корзину. Настройка типов, декларации, борьба с типами сторонних библиотек могут съесть больше времени, чем сэкономят. Для маленьких изолированных виджетов современный JS с аккуратной структурой — разумный выбор.
SSR, гидратация и риск для SEO
Здесь — главный подводный камень современного фронтенда, о который разбиваются коммерческие проекты. Классический SPA отдаёт браузеру почти пустой HTML и наполняет страницу через JavaScript уже на клиенте. Для пользователя с быстрым устройством это терпимо. Для поискового робота — проблема: контент, который появляется только после исполнения JS, индексируется хуже и позже. А для магазина каждая недоиндексированная страница — это потерянный трафик и деньги.
Отсюда три понятия, которые нужно различать:
- CSR (клиентский рендеринг). HTML почти пустой, всё рисует JS в браузере. Быстро в разработке, но плохо для SEO — контента в исходном HTML нет.
- SSR (серверный рендеринг фреймворка). Приложение рендерится в готовый HTML на сервере (Node.js), робот и пользователь получают контент сразу, затем JS «оживляет» страницу.
- Гидратация. Процесс, когда клиентский фреймворк «подхватывает» уже готовый серверный HTML и навешивает на него интерактивность, не перерисовывая с нуля.
Ключевой вывод для Битрикса: не превращайте индексируемый контент в CSR. И здесь у платформы огромное преимущество — её серверный рендеринг уже отдаёт готовый HTML каталога и карточек. Если вы используете точечные виджеты (наш основной сценарий), проблема SSR почти не встаёт: контент рендерит сам Битрикс на сервере, а виджет добавляет интерактив поверх уже проиндексированного HTML. По сути, серверный рендер Битрикса играет роль «SSR для контента», а фреймворк — роль гидратации интерактивных островков.
Проблема SSR становится острой только в headless-архитектуре, где фронтенд обособлен и сам отвечает за рендер. Тогда без серверного рендеринга (SSR/пререндер на Node.js) SEO проседает, и это приходится решать отдельной инфраструктурой. Именно поэтому headless — крайняя форма, а не «улучшенная версия сайта».
Как не потерять SEO при внедрении
Соберём практические правила, которые защищают SEO магазина при добавлении Vue или React. Они простые, но нарушение любого из них дорого обходится в выдаче.
- Индексируемый контент — только серверным HTML. Названия, описания, цены, характеристики товаров, тексты разделов должны быть в исходном HTML, который отдаёт Битрикс. Не переносите их в клиентский рендер.
- Виджеты — поверх контента, а не вместо него. Фильтр управляет выдачей, но сам список товаров пусть остаётся серверным. Виджет добавляет интерактив, не подменяя контент.
- Ссылки — настоящие
<a href>. Навигация по каталогу, пагинация, переходы в разделы — обычные ссылки на ЧПУ, доступные роботу, а не только клиентские обработчики. Виджет может перехватывать клик для плавности, но URL должен быть реальным. - Мета-теги ставит сервер. Title, description, canonical, микроразметку формирует Битрикс на сервере (в том числе в
component_epilog.php), а не JS на клиенте. - Проверяйте, что видит робот. Смотрите исходный HTML (view-source) и инструменты проверки индексации: контент должен присутствовать до исполнения JS.
Отдельно про фильтр — самое частое место риска. Умный фильтр на Vue легко «случайно» превратить в клиентский рендер списка, и тогда страницы категорий с параметрами перестают нормально индексироваться. Правильно: серверные страницы фильтрации (SEF-URL категорий и популярных комбинаций) отдаёт Битрикс, а виджет обеспечивает мгновенность внутри уже проиндексированной страницы. Как устроена SEO-дружественная фильтрация и её URL, мы разбираем в связке с каталогом — там это ключевая тема. Соблюдая эти правила, вы получаете живой интерфейс без жертв в поиске.
Производительность и размер бандла (CWV)
Второй по значимости риск после SEO — производительность. Каждый килобайт JavaScript, который вы отправляете пользователю, он оплачивает временем загрузки и отзывчивости. Фреймворк добавляет вес рантайма и вашего кода, и без контроля это напрямую бьёт по Core Web Vitals — метрикам, которые видит поиск и по которым его ранжирует. Подробно про сами метрики — в материале про Core Web Vitals для интернет-магазина.
Как современный фронтенд влияет на CWV и что с этим делать:
- LCP (скорость показа основного контента). Спасает то, что контент серверный — он показывается быстро, не дожидаясь JS. Не подменяйте основной контент клиентским рендером, и LCP не пострадает.
- INP (отзывчивость на действия). Тяжёлый бандл, который долго парсится и исполняется, ухудшает отзывчивость. Держите бандлы виджетов маленькими и грузите их не блокируя.
- CLS (визуальная стабильность). Когда виджет монтируется и «раздвигает» вёрстку, страница прыгает. Резервируйте место под контейнер заранее (задавайте размеры), чтобы гидратация не сдвигала контент.
Конкретные приёмы контроля веса и скорости:
- Отдельные бандлы на виджет. Не грузите один общий мега-бандл на все страницы. Фильтр нужен в каталоге, конфигуратор — на карточке; каждый едет только туда, где применяется.
- Ленивая загрузка. Грузите бандл виджета только на нужных страницах и, где уместно, отложенно — например, когда контейнер попадает в область видимости.
- Не дублируйте рантайм. Если виджетов несколько, выносите общий рантайм фреймворка в отдельный чанк, чтобы он не приезжал в каждом бандле.
- Следите за размером зависимостей. Тяжёлые компонентные библиотеки и утилиты незаметно раздувают бандл. Проверяйте вес сборки и берите только необходимое.
- Не блокируйте отрисовку. Скрипты виджетов подключайте с
defer/асинхронно, чтобы они не задерживали показ серверного контента.
Главная мысль: гибридная модель (серверный контент + лёгкие виджеты) сама по себе дружелюбна к CWV — быстрый первый показ обеспечивает сервер, а интерактив догружается точечно. Ломается это только при небрежности: гигантский общий бандл, клиентский рендер контента, отсутствие резервирования места. Дисциплина по бандлам — прямое условие хороших метрик, а значит и позиций.
Headless как крайняя форма
Headless — самая радикальная модель современного фронтенда. Здесь Битрикс перестаёт отдавать страницы вовсе: он превращается в «голову без тела» — бэкенд и источник данных через API, — а всю отрисовку берёт на себя отдельное фронтенд-приложение (нередко на React/Next.js или Vue/Nuxt) на своей инфраструктуре. Витрина и админка полностью разделены.
Что headless даёт и чего требует:
- Плюсы: полная свобода фронтенда, современный DX, возможность отдавать один API нескольким клиентам (веб, мобильное приложение), чистое разделение ответственности.
- Цена: нужен зрелый REST/GraphQL-слой, обязателен SSR или пререндер ради SEO, своя инфраструктура и деплой фронтенда, выше требования к команде и к бюджету. Многое из коробки Битрикса (композит, готовые компоненты, публичная правка) вы теряете и строите заново.
Честный вывод: headless оправдан не «для современности», а когда есть конкретные причины — сильная фронтенд-команда, потребность в едином API для нескольких клиентов, действительно приложение-уровня интерфейс. Для большинства магазинов это избыточно и рискованно: точечные виджеты дают 90% выгоды современного фронтенда при доле сложности и без потери сильных сторон платформы. Мы подробно разбираем эту модель, её условия и подводные камни в отдельном материале про headless-коммерцию на 1С-Битрикс — там же критерии, когда к ней стоит переходить, а когда нет.
Когда остаться на vanilla JS
Не каждая интерактивность требует фреймворка — и это тоже инженерное решение, а не отсталость. Огромная часть «динамики» магазина закрывается современным ванильным JavaScript и штатными расширениями ядра, без единого килобайта Vue или React.
Оставайтесь на vanilla, когда интерактив локальный и без разделяемого состояния:
- Показать/скрыть, табы, аккордеон, спойлеры. Пара обработчиков и переключение классов — фреймворк тут лишний.
- Модальные окна, тултипы, всплывашки. Часто уже есть в штатных UI-расширениях ядра — грузятся через
Extension::load. - Слайдеры и галереи. Готовая библиотека-слайдер без реактивности решает задачу.
- Валидация и отправка форм. Проверка полей и AJAX-отправка — десятки строк ванильного кода.
- «Показать ещё», плавная прокрутка, ленивые изображения. Точечные улучшения, не требующие стора.
Современный JavaScript силён: fetch, модули, делегирование событий, IntersectionObserver, шаблонные строки — этого хватает на большинство задач «оживления» страницы. А штатные UI-расширения Битрикса дают готовые попапы, уведомления и элементы без сторонних зависимостей. Прежде чем тянуть фреймворк ради одного слайдера, спросите себя: есть ли здесь настоящее состояние? Если нет — vanilla будет и легче, и быстрее, и проще в поддержке. Экономия сложности почти всегда выигрывает вдолгую.
Порог входа и поддержка команды
Технический выбор — это ещё и организационное решение. Внедряя Vue или React, вы меняете не только код, но и требования к команде, к найму и к передаче проекта. Недооценивать это опасно: красиво собранный виджет, который через год некому поддерживать, — плохая инвестиция.
Что важно взвесить заранее:
- Компетенции команды. Битрикс-разработчик и фронтенд-инженер на Vue/React — часто разные люди. Если команда чисто «битриксовая», добавление фреймворка требует нового навыка или найма.
- Единство стека. Разнобой (часть на Vue, часть на React, часть на vanilla) множит порог входа. Выберите один фреймворк и держитесь его на всём проекте.
- Инфраструктура сборки. Появляется npm, сборка, версии зависимостей, обновления. Это постоянная, а не разовая нагрузка на поддержку.
- Документация и передача. Виджеты нужно описывать: как собирать, где исходники, как устроено взаимодействие с бэкендом. Иначе новый разработчик потратит дни на археологию.
Практический вывод: соотносите сложность фронтенда с реальными силами команды и горизонтом поддержки проекта. Для небольшой команды разумнее меньше виджетов и максимум серверного рендеринга; для сильной фронтенд-команды планка выше. Каждый добавленный слой сложности должен окупаться пользой для пользователя — иначе это техдолг, который вы сами себе создаёте. Зрелое решение — то, которое проект сможет нести годами, а не только эффектно выглядит на демо.
Частые ошибки
Ошибки при внедрении современного фронтенда на Битриксе повторяются от проекта к проекту. Зная их заранее, вы избежите дорогих переделок и потери позиций.
- Переписать весь сайт в SPA. Каталог и карточки на клиентском рендере — прямой удар по SEO и скорости. Оставляйте контент серверным, фреймворк — точечно.
- Клиентский рендер индексируемого контента. Названия и описания товаров, появляющиеся только через JS, робот видит хуже. Контент — в серверном HTML.
- Фреймворк ради простого интерактива. Табы и слайдер на Vue — лишний бандл без пользы. Простое — на vanilla.
- REST там, где хватило бы ajax-действий. Поднимать внешний API ради виджета на своей же странице — лишняя работа и поверхность атаки. Внутри сайта — ajax-действия D7.
- Забытый CSRF-токен или токен в кэше. Изменяющие запросы без
sessidотклоняются; токен в кэшируемой части раздаётся всем один. Держите его свежим и некэшируемым. - Один гигантский бандл на все страницы. Пользователь каталога грузит и код конфигуратора, и код кабинета. Разбивайте по виджетам и грузите по месту.
- Скрипт
<script>посреди вёрстки. Ломает объединение ресурсов и композит. Подключайте черезaddExternalJs/AddHeadString. - Виджет прыгает при монтировании. Нет резервированного места — растёт CLS. Задавайте размеры контейнера заранее.
- Персональные данные в кэше компонента. Корзина или имя пользователя, попавшие в кэш, показываются всем. Персональное — в некэшируемые/динамические области.
- Нет исходников в репозитории. Правки в минифицированный бандл, потерянные исходники — виджет невозможно пересобрать. Исходники в git, сборка воспроизводима.
- Зоопарк фреймворков. Часть на Vue, часть на React — двойной порог входа и поддержки. Один фреймворк на проект.
Чек-лист внедрения
Короткий список для сверки при проектировании и перед релизом виджетов на магазине:
- Для каждого блока честно решено: серверный рендер/vanilla или Vue/React — по наличию богатого изменяемого состояния, а не «по моде».
- Индексируемый контент (каталог, карточки, тексты, мета) остаётся серверным HTML; виджеты работают поверх, не подменяя его.
- Виджеты монтируются в отдельные контейнеры внутри компонентов; остальная страница серверная.
- Стартовые данные отдаются из
$arResult(экранированно), изменения — через ajax-действия D7; REST только для внешних клиентов. - CSRF-токен (
sessid) передаётся в изменяющих запросах и хранится в некэшируемой области; права проверяются на действии. - Сборка настроена (Vite или Webpack, один на проект), исходники в репозитории, бандлы с хешем в имени,
node_modulesвне git. - Бандлы разбиты по виджетам и грузятся по месту; общий рантайм не дублируется; скрипты не блокируют отрисовку.
- Место под виджеты зарезервировано (нет скачков вёрстки — контроль CLS); подключение через
addExternalJs, а не тегом в вёрстке. - Стейт-менеджер введён только при реально разделяемом состоянии (корзина, большое приложение), не заранее.
- TypeScript применён там, где фронтенд большой и долгоживущий; для маленьких виджетов — по решению команды.
- Проверено, что робот видит контент в исходном HTML; ссылки — настоящие
<a href>на ЧПУ. - Команда владеет выбранным стеком, есть документация по сборке и взаимодействию с бэкендом.
Вывод
Современный фронтенд на 1С-Битрикс — это не выбор «серверный рендеринг или Vue/React», а грамотное сочетание. Битрикс силён серверной отдачей контента, кэшем, композитом и интеграцией с 1С — и для каталога, карточек, контентных страниц это оптимально по скорости и SEO. Фреймворк добавляют точечно, на участках с богатым изменяемым состоянием: умный фильтр, корзина, конфигуратор, личный кабинет, дашборд. Модель — виджеты в контейнерах внутри компонентов: серверный HTML остаётся основой, а Vue или React оживляют отдельные островки.
Практический стержень такой архитектуры складывается из нескольких дисциплин. Данные — стартовые из $arResult, изменяемые через ajax-действия контроллеров D7 (или REST для внешних клиентов), с обязательным CSRF-токеном. Сборка — Vite или Webpack через npm, исходники в репозитории, хешированные бандлы в папке шаблона, разбивка по виджетам ради Core Web Vitals. SEO защищено тем, что индексируемый контент рендерит сервер, а не клиент; headless остаётся крайней формой под доказанную потребность и сильную команду. И, наконец, простой интерактив честно оставляют на vanilla JS. Такой подход даёт живой современный интерфейс без жертв в поиске, скорости и поддерживаемости. Если нужен такой фронтенд под ваш проект, мы проектируем и внедряем его в рамках кастомной разработки на 1С-Битрикс и строим REST API для внешних клиентов. Другие материалы по архитектуре и производительности собраны в рубрике «Разработка и архитектура».