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.

