A client complains: every click in the personal account on a 1C-Bitrix site reloads the page, the user waits 2–3 seconds. We develop SPAs on React: after the first load, data is fetched via REST API, and React Router handles transitions without full reloads. As a result, pages load in 200–400 ms, server load drops by 4 times thanks to client-side caching. And no user tantrums.
Our portfolio includes over 30 SPA projects for Bitrix: from personal accounts to B2B portals with catalogs of 100,000 items. We have years of expertise in Bitrix development, which helps avoid typical integration mistakes. Contact us for a free audit of your project.
What problem does a React SPA solve?
The main pain of classic Bitrix sites is a full page reload on every action. The user clicks, the browser sends a request, the server generates HTML, the client receives and renders it. Each navigation takes 1–3 seconds. In a personal account, where a typical session includes 20–30 clicks, the total wait reaches a minute. SPA eliminates this: after loading the shell page, all subsequent transitions are handled on the client. Data is requested via REST API, and the interface updates instantly.
Architecture of a React SPA for Bitrix
Entry point and __INITIAL_STATE__
The SPA mounts through a single entry point in the Bitrix template. We inject initial data via window.__INITIAL_STATE__ — this reduces the number of requests at startup. As noted in React Router, this approach ensures smooth navigation without state loss.
Example of injecting initial state
// /local/templates/main/components/bitrix/system.auth.form/lk/template.php // Or a separate page /lk/index.php defined('B_PROLOG_INCLUDED') && (B_PROLOG_INCLUDED === true) || die(); ?> <!DOCTYPE html> <html> <head> <title>Personal Account</title> <?php $APPLICATION->ShowHead(); ?> </head> <body> <!-- Data for SPA initialization --> <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: navigation without reload
// /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) { // Redirect to standard Bitrix authorization 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> ); } Important: use basename in BrowserRouter so React Router knows the base path and doesn't break navigation.
State management: Zustand
For moderately complex SPAs, Zustand is the optimal choice over Redux. It requires less code and is easier to maintain.
// /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' } ) ); Error handling and offline mode
The SPA must handle network errors gracefully. We implement an Error Boundary that catches rendering errors and shows the user a recovery interface. For offline resilience, a Service Worker caches GET requests to the API.
// Global 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; } } Why SSR is not always needed?
For closed sections (personal account, admin panel), server-side rendering is not required — bots don't index them. For public pages (catalog, articles), we use one of the following approaches:
- Next.js with API requests to Bitrix on the server side — the cleanest approach.
- Vite SSR — harder to set up, but doesn't require changing the framework.
- Static site generation (SSG) — for rarely changing content.
The choice depends on the proportion of public content. If 80% of the functionality is a personal account, SSR is overkill.
How is SPA better than the classic approach?
Let's compare key metrics using an example B2B portal with a catalog of 50,000 items:
| Criteria | SPA (React) | MPA (standard) |
|---|---|---|
| Load time after first click | 200–500 ms | 1–3 s |
| Number of requests to server | 1 (data) | 1 (full page) |
| Server load | Low (cached) | High (generation) |
| Interface smoothness | High | Medium |
Additional table — comparison of rendering approaches:
| Approach | Speed | SEO | Complexity |
|---|---|---|---|
| CSR (client) | Fast after load | No | Low |
| SSR (server) | Fast first screen | Yes | High |
| SSG (static) | Instant | Yes | Medium |
SPA with CSR reduces interaction time by 60% and reduces the number of requests by 4 times — proven on projects. Savings on server infrastructure reach 40–60% of current costs.
Development process
- API analysis — determine what data Bitrix provides, design endpoints.
- Architecture design — choose the stack (React, Zustand, React Router, Vite build).
- Component development — create UI kit, pages, state management.
- Integration with Bitrix — connect REST API, configure CORS, inject initial state.
- Testing — load testing (1000 concurrent users), error scenarios.
- Deployment — set up CI/CD, caching, HTTPS.
Timeline and what's included
Timeline: from 2 to 6 weeks. The cost is calculated individually after an audit of your project. We do not use template solutions — each SPA is written for specific business needs.
The work includes:
- Source code in Git with commit history
- API and architecture documentation
- Deployment instructions
- Team training (2 hours online)
- 6-month warranty on identified bugs
- Post-launch support (optional)
Typical mistakes when developing an SPA on Bitrix
- Duplicating business logic in PHP and JS — keep the boundary clear: Bitrix only API, React only UI.
- Ignoring caching — tagged caching of Bitrix components must be configured.
- Missing Error Boundary — unhandled errors crash the entire SPA.
Our team's experience helps avoid these problems. Contact us for a free assessment of your project — we will analyze your current site and suggest the optimal architecture. Or order SPA development — we will estimate the task within one day.

