React SPA for Bitrix: Fast Personal Account

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

Our competencies:

Frequently Asked Questions

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

  1. API analysis — determine what data Bitrix provides, design endpoints.
  2. Architecture design — choose the stack (React, Zustand, React Router, Vite build).
  3. Component development — create UI kit, pages, state management.
  4. Integration with Bitrix — connect REST API, configure CORS, inject initial state.
  5. Testing — load testing (1000 concurrent users), error scenarios.
  6. 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.