Назад в блог

Изображения в headless Bitrix: что сломается первым

Мы запустили headless Bitrix + Next.js. LCP вырос — но не так, как ожидали.

Не на 30% вниз, как обещал CDN. Вырос вверх. 2.4 секунды вместо 1.6 секунд на монолите.

Три часа ушло на то, чтобы понять: Next.js отдаёт изображения через PHP-сервер, потому что мы не добавили remotePatterns для Bitrix-домена в next.config.js. Каждая картинка товара — новый запрос на PHP, полный проход через FPM, iResize, диск. CDN не в курсе.

Это решилось за 20 минут после того, как мы поняли, что происходит. Но понять заняло три часа.

Дальше — что именно происходит с изображениями в headless Bitrix и как не наступить на эти же грабли.

Как Bitrix работает с изображениями

Bitrix хранит оригиналы в /upload/. Это реальный каталог на диске: <site_root>/upload/iblock/abc/original.jpg.

Ресайзы генерируются через iResize при первом запросе и кладутся в /upload/resize_cache/. Путь выглядит примерно так: /upload/resize_cache/iblock/abc_600x400_exact/original.jpg. Второй запрос с теми же параметрами берёт уже готовый файл — PHP его просто отдаёт напрямую или через nginx.

Важная деталь: в типичном монолите /upload/ раздаётся nginx как статика. PHP вообще не участвует, если файл уже есть.

В headless-проекте это не так. Next.js Image по умолчанию проксирует внешние изображения через свой /_next/image-обработчик. Чтобы он не проксировал, а просто отдавал прямой URL, нужно явно указать домен Bitrix-сервера в remotePatterns. Без этого каждое изображение — это Next.js → Bitrix PHP → nginx → диск.

Это первая точка отказа.

Первая ошибка: /upload/ — не статика в headless

В монолите Bitrix /upload/ раздаётся напрямую nginx. В headless-схеме Next.js не знает, что https://bitrix.example.com/upload/resize_cache/... — это статический файл. Для него это просто URL внешнего ресурса.

Результат: если не настроить next.config.js, каждый <Image src="https://bitrix.example.com/upload/..."> будет проксирован через /_next/image. Next.js скачает изображение с Bitrix, пережмёт его под нужный размер и отдаст клиенту. Bitrix при этом тоже пережал — через iResize. Итого: двойная обработка, двойной сетевой трафик, лишняя нагрузка на Bitrix.

Фикс элементарный:

// next.config.js
images: {
  remotePatterns: [
    {
      protocol: 'https',
      hostname: 'bitrix.example.com',
      pathname: '/upload/**',
    },
  ],
}

Но этого недостаточно. Ещё нужно убедиться, что сам Bitrix-сервер раздаёт /upload/resize_cache/ как статику через nginx, а не через PHP. Мы видели конфигурации, где в nginx был location ~ \.php$ без явного исключения для /upload/ — и resize_cache шло через FPM. На проекте 28k SKU это создавало 15-20% лишней нагрузки на PHP-пул.

resize_cache: холодный старт в продакшне

iResize генерирует ресайзы лениво: при первом запросе файла с нужными размерами. На монолите это незаметно: Bitrix генерирует ресайз при рендере страницы на сервере, браузер получает уже готовый URL.

В headless всё иначе. Next.js получает PREVIEW_PICTURE из Bitrix REST API — это URL оригинала или последнего сгенерированного ресайза. Если вы запрашиваете размер, которого ещё нет в /upload/resize_cache/, первый запрос к этому URL запустит iResize: PHP откроет оригинал, пережмёт, сохранит файл. Это занимает 200-600 мс в зависимости от размера оригинала.

На каталоге 28k SKU после деплоя нового контента или после очистки resize_cache — это сотни холодных ресайзов в первые минуты работы сайта. LCP на страницах листинга резко вырастает.

Решения два. Первое — прогрев: отдельный скрипт, который после деплоя обходит sitemap и делает HTTP-запрос к каждому URL изображения с нужными размерами. Запускается как cron-задача или как хук деплоя. Второе — убедиться, что CDN кеширует /upload/resize_cache/ с достаточным TTL, чтобы холодный старт случался только один раз.

CDN перед Bitrix: три правила

CDN перед Bitrix-сервером — стандартная схема для headless-проектов. Но с изображениями нужно учесть несколько нюансов.

Первое — /upload/resize_cache/ кешировать можно и нужно. TTL от нескольких часов до нескольких дней. Bitrix не инвалидирует эти URL автоматически: старый файл лежит в resize_cache до явной очистки. Если вы обновили изображение товара в Bitrix, URL ресайза не изменится. Изменится только оригинал. При необходимости принудительного обновления — либо очищать resize_cache, либо добавлять версионный параметр к URL.

Второе — /upload/ (без resize_cache) кешировать осторожно. Там лежат оригиналы, которые могут обновляться. Если CDN жёстко закеширует старый оригинал, пользователи будут видеть устаревшие изображения.

Третье — PHP-приложение Bitrix не должно обрабатывать запросы к /upload/. Проверьте nginx-конфиг: location /upload/ должен раздаваться как статика (try_files $uri =404), без fastcgi_pass. На проекте 28k SKU перенос статики с PHP на nginx + CDN убрал около 40% нагрузки с app-сервера.

OG-картинки в headless: отдельная история

Когда Next.js генерирует <meta property="og:image">, он обычно берёт URL из frontmatter или из данных страницы. В headless Bitrix это чаще всего PREVIEW_PICTURE или DETAIL_PICTURE из REST API.

Проблема: OG-картинки шерятся в социальных сетях, мессенджерах, корпоративных чатах. Эти парсеры запрашивают изображение напрямую, игнорируя Next.js Image optimization. Они ожидают готовый JPEG или PNG по прямому URL.

Если CDN не кеширует этот URL, каждый шер страницы — это прямой запрос на Bitrix PHP. При вирусном распространении это заметно в метриках.

Что делать: выносить OG-картинки на CDN явно. Если Bitrix возвращает абсолютный URL вида https://bitrix.example.com/upload/..., убедитесь, что CDN этот URL покрывает и кеширует. Если возвращает относительный путь — собираете абсолютный URL в Next.js перед тем, как подставить в og:image.

Дополнительная проверка: OG-картинки должны быть минимум 1200×630px. Bitrix возвращает ресайзы с теми параметрами, которые вы запросили. Если вы не запросили нужный размер явно через параметры iResize, Facebook/LinkedIn может получить маленький превью или оригинал в 4K.

Чек-лист перед запуском media pipeline

Перед деплоем headless Bitrix + Next.js — пройтись по этому списку:

  • next.config.js: добавлен remotePatterns для Bitrix-домена с паттерном /upload/**
  • Nginx на Bitrix-сервере: /upload/ раздаётся как статика, PHP не участвует
  • CDN: /upload/resize_cache/** закеширован, TTL выставлен явно
  • CDN: /upload/ (оригиналы) — политика кеширования проверена, не конфликтует с обновлениями
  • Warm-up скрипт: запускается после каждого деплоя нового контента
  • OG-картинки: размер ≥ 1200×630px, URL абсолютный, CDN покрывает путь
  • Мониторинг: LCP на страницах листинга после деплоя — первая точка для проверки

Проверить можно через Chrome DevTools → Network → Images: смотреть на строки с /upload/, убедиться, что Cache-Control не no-cache, видеть X-Cache: HIT от CDN.


Media pipeline в headless — это не сложно. Но это отдельная система, которую нужно спроектировать, а не «просто вставить <Image>». Большинство регрессий LCP, которые я видел в headless-проектах на Bitrix, были именно здесь.

Про другие production-сюрпризы headless Bitrix REST API — в этой статье. Про кеш-инвалидацию через Next.js ISR — здесь.