Back to blog

One Bitrix backend, two Next.js frontends: what actually breaks

One PHP backend. Two Next.js frontends. Two domains. Two brands.

This isn't the Bitrix MultiSite feature from the docs. It's a custom headless multi-site architecture built from necessity. It works — with a few failure modes we discovered the hard way.

Why one Bitrix for two frontends (it's not about cost)

The real reason: shared data.

When we launched the second site, 1C synchronization, supplier catalogs, and order logic already lived in one Bitrix instance. Standing up a second instance would mean keeping two copies of this data in sync. That's worse.

The cost savings on server infrastructure are a side effect. The main argument is a single source of truth for business data.

The first version looked simple: one PHP backend, two frontends on separate domains, REST API without domain-level authentication. A week after launch, the first failure arrived as session chaos.

API namespacing: the first architectural decision that matters

The one thing we got right before launch: separate namespaces.

/api/ivanpin/services/
/api/services/wgp/

This isn't just a folder structure. It's a boundary of responsibility.

Each namespace has its own _common.php with IBLOCK_ID mappings, token validation, and a defined set of allowed operations. When frontend A makes a request, it hits its namespace and can't accidentally read data belonging to frontend B. The X-WGP-Token header is shared at the backend level, but validated by a namespace-specific handler.

This worked. But only for access isolation. Data isolation is a separate problem.

Content isolation via IBLOCK_ID: the trade-off no one mentions

IBLOCK_ID is the primary content isolation unit in Bitrix. Each site running off the same backend needs its own set of info block IDs to keep data separate.

For ivanpin.com: 40 (Blog RU) and 41 (Blog EN). For the second site — different IDs, different rules.

The function ivanpinApiResolveIblockId in _common.php handles the mapping on every request. That part works.

The problem is cache contention. Under load, when both sites simultaneously hit Bitrix entities — PHP processes compete for the same cache locks. Bitrix composite cache doesn't know about your two separate frontends. It sees one site.

We hit this twice: one frontend invalidates the cache, the second gets a cold response that needs regeneration under load. TTFB on the second site spikes by 200–400ms, exactly when the first site is actively writing data.

The fix: separate cache sections using CACHE_ID. Each namespace sets its own cache salt. Invalidating one site's cache doesn't touch the other.

Session handling across two domains: the failure and the fix

Cross-domain session leakage in Bitrix headless happens when PHPSESSID cookies aren't namespaced per site. A week after the second site launched, we caught a strange bug: an authenticated user on site A was seeing data belonging to a user on site B.

Diagnosis took a day. The cause: PHPSESSID cookie without a Domain scope.

Bitrix issues session cookies without an explicit domain by default. If both sites share the same PHP session backend — Redis in our case — and two different browsers from different sites happen to get the same session ID (a collision), they get each other's data.

The collision probability is low. But it's not zero — especially after a Redis cold start following a server restart.

The fix: explicit session_name() per namespace. For ivanpin: IVANPIN_SESSID. For the second site: WGP_SESSID. One line of code per namespace. Closed an entire class of problems permanently.

CDN with a shared backend: cache keys you have to hardcode

A CDN serving a single Bitrix backend for multiple sites caches by URL only — it has no awareness of which frontend originated the request. Without explicit Vary: Origin rules, one site's cache invalidation can surface stale content to the other.

For API endpoints, this isn't a problem: /api/ivanpin/services/blog/list.php and /api/services/wgp/posts/list.php are different URLs and CDN handles them correctly.

The problem came with shared resources. Images from /upload/ on the backend share a URL space between both sites. CDN cached /upload/iblock/123/image.jpg once across all origin domains.

When one site updated an image — new file, same filename — the second site still saw the old cached version for a few minutes.

The fix: Vary: Origin in CDN rules. Now the same URL from different origin domains gets its own cache entry. Cache hit rate dropped slightly — from around 89% to 84% — but correctness matters more.

When to split the backends: three triggers

We haven't split ours. But there are three situations where that would be the right call.

First: the two sites have fundamentally different uptime and deploy requirements. A Bitrix update affects both sites simultaneously. If that's critical for one but acceptable for the other, separate backends make sense.

Second: one site starts generating load that affects the other. One PHP-FPM pool shared between two sites. If site B runs a marketing campaign and gets 10x traffic, site A will feel it. Separate PHP-FPM pools help, but don't fully solve it.

Third: data security requirements diverge. GDPR, personal data handling, different jurisdictions — separate backends give you cleaner isolation and a simpler audit trail.

If none of these apply, a single backend is defensible. The complexity of splitting is higher than it looks: syncing shared reference data, running dual 1C integration, maintaining two PHP CI/CD pipelines.


One Bitrix serving two headless frontends isn't a Bitrix feature. It's a manual assembly job: namespace it, salt the cache, rename the sessions. Most of the problems get solved once and don't come back. Finding them the first time is expensive.

Internal links: Bitrix REST API: five surprises · Headless is about deploy independence · PHP Earns the Money. Next.js Shows It.