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.
What Does React Development for 1C-Bitrix Provide and When Is It Needed?
A catalog with 30,000 SKUs and a faceted filter — the standard Bitrix template loads the page in 3 seconds. A B2B cabinet with personalized discounts — price recalculation with every filter change. The bottleneck is not the data but the monolithic architecture: each block calls REST separately, 6–8 sequential requests of 200–500 ms result in a total delay of 2–3 seconds. React solves this radically: component model, virtual DOM, and library ecosystem turn a slow interface into a responsive application. We are a certified 1C-Bitrix partner with 10+ years of experience and over 500 completed projects. Order an audit — we will evaluate your project in 1–2 days and show cases similar to yours.
Architectural Approaches
SPA on React + REST API Bitrix (BX.rest)
The React application lives separately, accessing /rest/ or custom endpoints via CRestServer. Maximum control, but also maximum work.
- Client-side routing with React Router — transitions without reload, but on F5 you need a catch-all on Nginx:
try_files $uri /index.html
- Optimistic updates: cart updates instantly,
sale.basket.update flies in the background. On error, roll back state and show a toast
- Frontend deploys to CDN independent of Bitrix — updated a button without touching the backend
SSR with Hydration — When Yandex Doesn't See SPA
Yandex has learned to render JS, but not perfectly; Googlebot is better, but still not 100%. Server-side rendering of React components via Node.js solves the problem radically: the bot receives ready HTML, the user gets an interactive application after hydration. FCP drops below one second on normal hosting, og:title and og:image work for social networks. The complexity is needing a Node.js process alongside Apache/Nginx serving Bitrix: two runtimes, two deploys, two log sets. Bitrix cache (CPHPCache, Composite) can be used to warm data that later goes to SSR.
Headless Bitrix — Admin Panel for Content Managers, React for Visitors
Content manager logs into /bitrix/admin/, edits infoblocks. The visitor sees a React application that retrieves data via API. One backend serves the site, mobile app, and Telegram bot. Scaling: React bundle on CloudFront/CDN, Bitrix on a single server. With 50,000 unique visitors, the frontend does not directly load the backend. Pitfall: the standard Bitrix visual editor (BXEditor) stops working for visitors — content managers will have to work only through the admin panel.
Stack and Component Architecture
Stack We Actually Use
| Technology |
Why It's Used |
| React 18+ |
Suspense, useTransition — UI does not block during heavy catalog updates |
| TypeScript |
Typing Bitrix API responses — IBlockElement, BasketItem, Order. Without it, refactoring is Russian roulette |
| Vite |
HMR in 50 ms vs 3–5 s for webpack. On a project with 200 components, the difference is enormous |
| React Query |
useQuery(['catalog', sectionId]) — automatic cache, revalidation, retry on 503 from overloaded Bitrix |
| React Hook Form + Zod |
Order form: 15–20 fields, conditional validation (legal entity — one set, individual — another). RHF does not re-render form on every keystroke |
| Tailwind CSS |
Utility classes — no fighting with cascades from Bitrix's template_styles.css |
| Radix UI / Shadcn |
Accessible primitives with ARIA out of the box |
How We Build Components and Type Data
Every project starts with a design system — otherwise, by the third month, three developers will write three different button components. Typography, colors, spacing — via CSS variables and Tailwind config. Forms: inputs with masks (phone, INN), searchable selects, file upload with preview and MIME validation. Product card is a separate story: price with discounts from CCatalogProduct::GetOptimalPrice(), labels "Hit"/"New" from infoblock properties, "Add to cart" button with loading/success/error states. Tables with virtualization (react-window) for price lists of 5000+ rows.
We type everything that comes from Bitrix. The REST API returns string where you'd expect number, "Y"/"N" instead of boolean, and null instead of empty array. A Zod schema on input parses and transforms — components get proper types.
// Real type of CIBlockElement via REST — surprises everywhere
interface BitrixProduct {
ID: string; // yes, string, not number
ACTIVE: "Y" | "N"; // not boolean
PRICE: string; // also string
QUANTITY: string; // and this is string
}
Performance and API Integration
Aggregating endpoints are key. One ajax.php or custom controller on \Bitrix\Main\Engine\Controller gathers catalog data, filters, cart, and user in a single request. React Query caches the response, and a second visit returns from cache with staleTime — load time reduces by 60% already on the second load.
How Does React Improve Core Web Vitals?
-
LCP < 2.5 s — lazy loading images via
loading="lazy", inline critical CSS, preload LCP image via <link rel="preload">
-
INP (replaces FID) < 200 ms —
useTransition for heavy filtering, useDeferredValue for search input
-
CLS < 0.1 — fixed sizes for skeletons and images. Skeleton placeholders instead of spinners
Virtualization is not optional, but necessary. A catalog with faceted filter may return 500 products per page. React-window or react-virtuoso render only the visible 20–30 cards — DOM does not bloat, scrolling is smooth.
REST and Custom Controllers
Out of the box via /rest/: infoblocks (iblock.element.get), cart (sale.basket.*), orders (sale.order.*), users (user.*). For a simple catalog, that's enough. But 70% of tasks require custom endpoints. \Bitrix\Main\Engine\Controller is the standard way to create your own endpoints in D7. Write a controller, register via registerAction, get endpoint with CSRF protection and authorization out of the box.
- Aggregation: one request = catalog data + filters + cart + user
- WebSocket via Bitrix Push & Pull (
CPullStack::AddByTag) — order status updates in real time, no polling
- GraphQL middleware (webonyx/graphql-php) on top of D7 ORM — frontend requests exactly the fields it needs. Mobile traffic savings up to 40%
Projects, Timelines, and What's Included
Typical Projects We Have Already Done
- Online store with 30,000 SKUs and faceted filter via
\Bitrix\Iblock\PropertyIndex\Facet — SPA, React Query, catalog virtualization
- B2B cabinet: personalized prices from
CCatalogGroup, reconciliation statements from 1C via \Bitrix\Sale\Compatible\OrderCompatibility, order history with filtering
- Corporate portal: dashboards on Recharts, real-time via Push & Pull, integration with internal APIs through middleware
- Marketplace: two React applications (buyer + seller), common backend, data separation via
CUser::GetUserGroup()
Timelines and What's Included
| Project Type |
Timeline |
| Landing page on React + Bitrix |
2–4 weeks |
| SPA online store |
8–16 weeks |
| Corporate portal |
10–20 weeks |
| Gradual frontend migration to React |
6–12 weeks |
- Audit of current Bitrix code and architecture
- Design of API layer (REST / custom controllers / GraphQL)
- Development of design system and components
- CI/CD setup (deploy React bundle independently of Bitrix)
- Documentation of endpoints and types (Swagger / TypeScript types)
- Transfer of access to server, admin panel, repository
- Training content managers to work through the admin panel
- Warranty support for 2 months after delivery
Exact numbers after scope analysis. Assessment is phased, with a fixed budget for each sprint.
How We Implement React in a Project
- Audit existing code — find bottlenecks: redundant requests, outdated templates, suboptimal caches.
- Design API layer — determine which endpoints are needed, design aggregators or GraphQL.
- Develop design system — create components (buttons, forms, cards) based on mockups or UX recommendations.
- Integrate with Bitrix via chosen approach (SPA, SSR, or Headless) — set up rendering and routing.
- Test and deploy — launch a pilot section (e.g., catalog), measure Core Web Vitals, upon approval expand.
Typical Mistakes When Implementing React in Bitrix
- Ignoring Bitrix caching — React Query may conflict with composite cache if tagged caching is not configured.
- Lack of error handling from REST — on a 500 error, the interface may "freeze". Need a global handler with fallback UI.
- Too many micro-components — each small widget calls API. Better to aggregate data in a single request.
- Wrong hydration order in SSR — data from the server must exactly match the client's initial state, otherwise React hydration errors.
Why React, Not Vue or Bitrix Templates
- Ecosystem. For any UI task, there is a ready library: tables, charts, drag-and-drop, virtualization. For Vue, the choice is narrower; for Bitrix templates, almost absent.
- Talent pool. Finding a React developer is three times easier than a Bitrix templater who knows D7 and
template.php.
- React Native. Components are reused in mobile app — not one-to-one, but business logic and types are shared.
- Gradual adoption. Start with one section (
/catalog/) on React, keep the rest on Bitrix templates. component_epilog.php loads the React bundle, data is passed via window.__INITIAL_DATA__.
1C-Bitrix + React is not a theoretical architecture but a working combination that already serves catalogs with tens of thousands of SKUs and B2B cabinets with heavy business logic. Learn more about React and 1C-Bitrix. Get a consultation — we will send you cases similar to your project. Contact us to discuss details. We implement turnkey with a guarantee of results.