Розробка 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 не потрібен — економте на складності.
Обробка помилок і офлайн-режим
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;
}
}
Для офлайн-стійкості Service Worker кешує GET-запити до API. При втраті з'єднання користувач бачить кешовані дані з позначкою «офлайн-режим», а POST-запити ставляться в чергу і виконуються при відновленні мережі.
SPA у Бітрікс-проєкті потребує дисципліни на стику двох систем: Бітрікс має давати дані через API, React — лише відображати та обробляти введення. Як тільки бізнес-логіка починає дублюватись — частина в PHP-компонентах, частина в JS — проєкт стає нечитабельним. Тримайте межу чіткою.







