Разработка SPA на React для 1С-Битрикс
Single Page Application поверх 1С-Битрикс — это когда браузер загружает одну HTML-страницу, а дальнейшая навигация происходит без полной перезагрузки. Битрикс отдаёт данные через API, React Router управляет URL и переходами. Подходит для личных кабинетов, B2B-порталов, административных интерфейсов — везде, где пользователь долго работает в системе и перезагрузка страницы разрушает UX.
Структура SPA и точка входа
SPA монтируется через единственную точку входа в Битрикс-шаблоне:
// /local/templates/main/components/bitrix/system.auth.form/lk/template.php
// Или отдельная страница /lk/index.php
defined('B_PROLOG_INCLUDED') && (B_PROLOG_INCLUDED === true) || die();
?>
<!DOCTYPE html>
<html>
<head>
<title>Личный кабинет</title>
<?php $APPLICATION->ShowHead(); ?>
</head>
<body>
<!-- Данные для инициализации SPA -->
<script>
window.__INITIAL_STATE__ = <?= json_encode([
'user' => $arResult['USER'],
'csrfToken'=> bitrix_sessid(),
'apiBase' => '/local/ajax/',
]) ?>;
</script>
<div id="spa-root"></div>
<?php $APPLICATION->ShowFooter(); ?>
<script type="module" src="/local/js/dist/lk.js"></script>
</body>
</html>
React Router: маршрутизация SPA
// /local/js/src/lk/App.tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
export function App() {
const { data: auth } = useAuth();
if (!auth?.isAuthorized) {
// Редирект на стандартную авторизацию Битрикс
window.location.href = '/auth/?backurl=' + encodeURIComponent(window.location.pathname);
return null;
}
return (
<BrowserRouter basename="/lk">
<AppLayout>
<Routes>
<Route index element={<Dashboard />} />
<Route path="orders" element={<Orders />} />
<Route path="orders/:id" element={<OrderDetail />} />
<Route path="profile" element={<Profile />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</AppLayout>
</BrowserRouter>
);
}
Важно: используйте basename в BrowserRouter, чтобы React Router знал базовый путь и не ломал навигацию.
Управление состоянием: Zustand
Для SPA с умеренной сложностью Zustand — оптимальный выбор вместо Redux:
// /local/js/src/lk/store/cartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { bitrixApi } from '../api/bitrix';
interface CartState {
items: CartItem[];
total: number;
addItem: (productId: number, quantity: number) => Promise<void>;
removeItem: (itemId: number) => Promise<void>;
syncWithServer: () => Promise<void>;
}
export const useCartStore = create<CartState>()(
persist(
(set, get) => ({
items: [],
total: 0,
addItem: async (productId, quantity) => {
const result = await bitrixApi.post<{ items: CartItem[]; total: number }>(
'cart.add',
{ product_id: productId, quantity }
);
set({ items: result.items, total: result.total });
},
removeItem: async (itemId) => {
const result = await bitrixApi.post<{ items: CartItem[]; total: number }>(
'cart.remove',
{ item_id: itemId }
);
set({ items: result.items, total: result.total });
},
syncWithServer: async () => {
const result = await bitrixApi.get<{ items: CartItem[]; total: number }>('cart.get');
set({ items: result.items, total: result.total });
},
}),
{ name: 'bitrix-cart' }
)
);
Серверный рендеринг (SSR) — нужен ли?
SPA на клиентском рендеринге имеет проблему: поисковые боты плохо индексируют контент, который генерируется JS. Для личного кабинета (закрытый раздел) это не важно. Для публичного каталога — критично.
Если SPA включает публичные страницы, нужен SSR. Варианты:
- Next.js с API-запросами к Битриксу на стороне сервера — наиболее чистый подход
- Vite SSR — сложнее в настройке, но не требует смены фреймворка
- Пре-рендеринг статических страниц (SSG) для страниц с редко меняющимся контентом
Для закрытых разделов (личный кабинет, B2B-портал) SSR не нужен — экономьте на сложности.
Обработка ошибок и offline-режим
SPA должен корректно обрабатывать ошибки сети и API:
// Глобальный Error Boundary
class ApiErrorBoundary extends Component<Props, State> {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return <ErrorScreen error={this.state.error} onRetry={() =>
this.setState({ hasError: false })} />;
}
return this.props.children;
}
}
Для offline-устойчивости Service Worker кэширует GET-запросы к API. При потере соединения пользователь видит кэшированные данные с пометкой «офлайн-режим», а POST-запросы ставятся в очередь и выполняются при восстановлении сети.
SPA в Битрикс-проекте требует дисциплины на стыке двух систем: Битрикс должен давать данные через API, React — только отображать и обрабатывать ввод. Как только бизнес-логика начинает дублироваться — часть в PHP-компонентах, часть в JS — проект становится нечитаемым. Держите границу чёткой.







