Один Bitrix — два фронтенда: что реально ломается
У нас один PHP-бэкенд. Два Next.js-фронтенда. Два домена. Два бренда.
Это Bitrix headless с мультисайтовым API — не MultiSite из документации, а кастомная архитектура, собранная из необходимости. Она работает. Но с несколькими оговорками, которые мы узнали через боль.
Зачем один Bitrix на два фронтенда
Не потому что дёшево. Потому что данные общие.
Когда запускали второй сайт, 1C-синхронизация, справочники поставщиков и логика заказов уже жили в одном Bitrix. Поднимать второй инстанс означало бы держать два экземпляра этих данных в синхронизации. Это хуже.
Экономия на сервере — побочный эффект. Главный аргумент: одна точка правды для бизнес-данных.
Первая версия архитектуры выглядела просто: один PHP, два фронтенда на отдельных доменах, REST API без аутентификации на уровне домена. Через неделю после запуска первая проблема пришла в виде сессионного хаоса.
API-namespace как первое архитектурное решение
Первое, что мы сделали правильно: разделили namespace'ы ещё до запуска.
/api/ivanpin/services/
/api/services/wgp/
Это не просто структура папок. Это граница ответственности.
Каждый namespace имеет свой _common.php с маппингом, токеном и набором разрешённых операций. Когда фронтенд сайта A делает запрос — он попадает в свой namespace и не может случайно прочитать данные сайта B. Токен X-WGP-Token в заголовке — общий для бэкенда, но его проверяет namespace-конкретный обработчик.
Это сработало. Но только для изоляции доступа. Изоляция данных — отдельная история.
IBLOCK_ID как единица изоляции: что с этим не так
В Bitrix данные живут в инфоблоках. У каждого сайта — свои IBLOCK_ID.
Для ivanpin.com: 40 (Блог RU) и 41 (Блог EN). Для webgoodpeople.com — другие ID, другие правила.
Функция ivanpinApiResolveIblockId в _common.php делает маппинг при каждом запросе. Это работает.
Проблема в другом. Под нагрузкой, когда оба сайта одновременно делают запросы к одним и тем же сущностям Bitrix — процессы PHP начинают конкурировать за одни и те же блокировки кэша. Bitrix composite cache не знает про то, что у тебя два разных фронтенда. Он видит один сайт.
Два раза ловили ситуацию: один фронтенд инвалидирует кэш, второй получает холодный ответ, который генерируется заново под нагрузкой. TTFB на второй сайт вырастает на 200-400 мс — именно в те моменты, когда первый сайт активно пишет данные.
Решение простое, но неочевидное: отдельные секции кэша через CACHE_ID. Каждый namespace прописывает свой cache-salt. Теперь инвалидация одного сайта не задевает кэш второго.
Сессии на двух доменах: где утекало
Через неделю после запуска второго сайта получили странный баг: авторизованный пользователь на сайте A случайно видел данные пользователя B.
Диагноз занял день. Причина — PHPSESSID cookie без Domain-scope.
Bitrix по умолчанию выдаёт session cookie без явного домена. Если оба сайта висят на одном сервере и у них общий домен верхнего уровня — браузер может отправить один и тот же PHPSESSID на оба домена.
У нас домены были разными (ivanpin.com и webgoodpeople.com). Но оба сайта использовали одни PHP-сессии на сервере. Bitrix идентифицировал пользователя по PHPSESSID в Redis — и если два разных браузера с разных сайтов случайно имели одинаковый session ID (коллизия), получали данные друг друга.
Вероятность коллизии низкая, но не нулевая. Особенно при cold start Redis после перезагрузки.
Фикс: принудительный session_name() разный для каждого namespace. Для ivanpin: IVANPIN_SESSID. Для WGP: WGP_SESSID. Простое изменение, которое закрыло класс проблем навсегда.
CDN и общий бэкенд: cache-ключи вручную
CDN не знает про то, что у тебя два сайта. Он кэширует по URL.
Если /api/ivanpin/services/blog/list.php и /api/services/wgp/posts/list.php — это разные URL, CDN всё сделает правильно. И у нас так.
Проблема возникла с общими ресурсами. Изображения из /upload/ на бэкенде — это один URL-пространство для обоих сайтов. CDN кэшировал /upload/iblock/123/image.jpg один раз для всех доменов.
Когда один сайт обновлял картинку (новый файл с тем же именем) — второй сайт ещё несколько минут видел старую версию из CDN-кэша. Это не катастрофа, но раздражало.
Решение: в CDN-правилах прописали Vary: Origin. Теперь одинаковый URL с разных доменов-источников кэшируется отдельно. Cache hit rate немного упал — примерно с 89% до 84% — но корректность важнее.
Три триггера для разделения бэкендов
Мы не разделяем бэкенды. Но есть три ситуации, когда это стало бы правильным решением.
Первый: разные требования к uptime и деплою. Bitrix-обновление затрагивает оба сайта одновременно. Если для одного сайта это критично, а второй может потерпеть — лучше разделить.
Второй: один сайт начинает давить нагрузкой на второй. Один PHP pool на двоих. Если сайт B запустил маркетинговую кампанию и получил 10x трафик — сайт A это почувствует. Отдельные PHP-FPM pools помогают, но не решают полностью.
Третий: требования к безопасности данных расходятся. GDPR, персональные данные, разные юрисдикции — при отдельных бэкендах изоляция чище и проще аудировать.
Если ни одного из этих триггеров нет — один бэкенд оправдан. Сложность разделения выше, чем кажется: синхронизация общих справочников, двойная 1C-интеграция, два CI/CD пайплайна для PHP.
Один Bitrix на два headless-фронтенда — это не мультисайт-фича из коробки. Это ручная сборка: namespace, cache-salt, session_name. Большинство проблем решаются один раз и не возвращаются. Найти их в первый раз — дорого.
Внутренние ссылки: Bitrix REST API: пять сюрпризов · Headless — это про независимость деплоя · PHP зарабатывает деньги. Next.js их показывает.