~/oybek.dev
статьи
Architecture1 июня 2026 г.· 10 мин чтения

Как мы запустили региональную EV-платформу NIO — архитектурные решения, которые сыграли роль

Разбор кейса региональной EV-платформы NIO: кастомный конфигуратор, синхронизация товаров через MedusaJS, запись в шоурум и самостоятельная Sanity CMS — какие архитектурные решения сработали и что я бы изменил.

Когда глобальный бренд электромобилей попросил нас запустить его региональную платформу в регионе MENA, бриф звучал не как «сделайте сайт». Нужны были конфигуратор, коммерческий бэкенд, флоу записи в шоурум и контентная система, которой региональная команда маркетинга могла бы управлять, ни разу не заводя тикет на разработчика, — и всё это к дате запуска, которую никто не собирался сдвигать. Самое интересное в региональной EV-платформе NIO — не список фич. Это headless-архитектура электронной коммерции под капотом: то, как фронтенд на Next.js, CMS Sanity и коммерческое ядро MedusaJS были связаны так, что четыре отдельные системы выглядели для покупателя как один продукт и при этом оставались дешёвыми в изменении для команды. Это разбор по существу — решения, которые сыграли роль, то, что выдержало реальный региональный трафик, и то, что я сделал бы иначе.

Высокоуровневая диаграмма архитектуры: фронтенд на Next.js, соединённый с Sanity, MedusaJS и сервисом записи
Четыре системы за одной витриной

Ограничение, которое определило каждое архитектурное решение

NIO пришли с ограничениями, которых ждёшь от глобального автомобильного бренда: жёсткие бренд-стандарты, очередь из стейкхолдеров и непреклонный дедлайн. Но ограничением, которое на самом деле определило архитектуру, было самое скучное — передача проекта. Что бы мы ни построили, этим должна была уметь управлять региональная команда маркетинга без единого инженера в штате, и она должна была продолжать этим управлять год спустя, когда нас на проекте уже не будет.

Одно это требование убило кучу соблазнительных срезов углов. Контент не мог жить в коде. Цены не могли быть захардкоженным JSON-файлом, о существовании которого кто-то забудет. А коммерческий слой должен был быть тем, чем мы владеем и что можем передать, — не SaaS-аккаунтом с привязанной к нему дорожной картой вендора. Если вы читали, почему я опираюсь на headless-решения для серьёзной коммерции, то здесь весь этот аргумент в одном проекте.

Почему мы построили кастомный конфигуратор, а не взяли готовый

Конфигуратор автомобиля — очевидное место, где так и тянет взять сторонний виджет. Мы не стали, и я бы каждый раз принимал это решение снова.

Готовые автомобильные конфигураторы оптимизированы под каталог дилера, а не под опыт бренда. Они приходят со своим DOM, со своими лазейками для стилизации и со своим представлением о том, как устроено ценообразование, — которое никогда не совпадает с регионом, где пошлины, пакеты опций и валюта отличаются от глобального дефолта. В тот момент, когда конфигуратор должен учитывать специфичные для MENA правила ценообразования и при этом ощущаться как NIO, а не как универсальный плагин, виджет «для экономии времени» превращается в то, с чем ты воюешь три спринта.

Поэтому мы построили его как стейт-машину на React поверх дерева модели, комплектации, цвета и опций, с расчётом цены вживую на стороне коммерческого слоя, а не вшитой во фронтенд. Задача конфигуратора — отрисовать варианты и вычислить корректный выбор; *цена* этого выбора — всегда ответ коммерческого слоя. Именно это разделение позволило менять цены без деплоя — и именно поэтому региональное обновление цен ни разу не затронуло код фронтенда.

Интерфейс конфигуратора автомобиля в процессе выбора, открыта панель комплектации и цвета с актуальной ценой
Состояние конфигуратора на клиенте, цены из Medusa

MedusaJS как источник истины, а не кнопка оформления заказа

Принято считать, что «headless-коммерция» — это SDK оформления заказа, прикрученный к маркетинговому сайту. На NIO всё было наоборот: MedusaJS был хребтом, а витрина — лишь его представлением для чтения.

Автомобили, аксессуары, комплектации, региональные цены и наличие — всё живёт в Medusa как единый источник истины. Приложение на Next.js читает оттуда, конфигуратор сверяется с этим, а флоу шоурума привязывает лиды к тому же каталогу. Мы выбрали Medusa именно потому, что это open-source и полностью наша собственность — никакой посемейной SaaS-тарификации, никакого каталога, с которого пришлось бы мигрировать при смене контракта, и модульная система, которую можно расширить под те части автомобильной коммерции, что ни одна платформа не даёт из коробки. Полное дерево решений Hydrogen против Medusa я расписал отдельно, но коротко для NIO: сложный каталог с региональными ценами, которым клиенту нужно владеть от и до, указывает на Medusa, а не на Shopify Hydrogen.

Синхронизация между Medusa и витриной — это место, где headless-архитектуры обычно гниют. Ошибка в том, чтобы относиться к фронтенду как к кэшу, который нужно вручную инвалидировать, — в итоге получаешь кладбище ревалидационных вебхуков, которым никто не доверяет. Мы держали путь чтения простым: витрина подтягивает данные каталога и цен типизированными серверными вызовами в момент запроса для всего ценочувствительного и статически генерирует только по-настоящему статическую маркетинговую оболочку. Неверная цена на автомобиль хуже, чем чуть более медленная страница, — а серверные компоненты позволяют делать этот компромисс на уровне маршрута, а не для всего приложения сразу.

CMS, которой команда маркетинга действительно пользуется

Причина, по которой в этом стеке стоит Sanity, а не более тяжёлая традиционная CMS, — снова требование передачи проекта. Команда MENA публикует кампании, обновления страниц моделей и новости ежедневно, на двух языках, без разработчика в цепочке. Это работает только если модель контента выстроена вокруг *того, что редактирует команда*, а не вокруг вёрстки страницы.

Поэтому мы смоделировали структурированные типы контента — кампания, страница модели, новостная запись — с локализацией на уровне полей, а не дублированием документов под каждый язык. Редактор меняет заголовок один раз и правит перевод прямо рядом с ним; фронтенд резолвит запрошенную локаль с откатом на английский. Урок, который я раз за разом переучиваю и описал в Sanity в реальном мире, в том, что разница между демо CMS и CMS, которой всё ещё пользуются ежедневно год спустя, почти целиком в моделировании контента и превью. Мы дали им настоящее визуальное редактирование — кликнул по заголовку в живом превью, поправил его на месте — потому что команда маркетинга, которая видит своё изменение до публикации, действительно публикует.

Тест CMS — это не демо на запуске. Это то, редактирует ли кто-то в ней двенадцать месяцев спустя, не звоня вам.
Sanity Studio, открытая рядом с живым превью страницы модели NIO, редактор в процессе правки
Локализация на уровне полей и визуальное редактирование по клику

Запись в шоурум: неприметная система, которая отбила своё

Флоу записи — тест-драйвы и визиты в шоурум — выглядел как самый маленький кусок, а оказался тем, что бизнес ценил больше всего, потому что именно здесь платформа производит лиды. Мы завели записи в воронку лидов со сквозным трекингом, чтобы маркетинг видел весь путь от взаимодействия с конфигуратором до забронированного визита, а не отправку формы, висящую в изоляции.

Технически здесь нет ничего сложного. Архитектурным решением, которое сыграло роль, было держать сервис записи отдельной ограниченной зоной ответственности, которая *ссылается* на каталог, а не переписывает его заново, — так что модель, ушедшая в out of stock в Medusa, отражается в том, на что можно записаться, при нуле дублированных данных.

Что выдержало — и что я сделал бы иначе

Платформа запустилась в срок и с тех пор в продакшене: конфигуратор, записи и синхронизация коммерции обслуживают реальных покупателей по всему региону. Эта же архитектура стала фундаментом для нашего следующего регионального автомобильного проекта — витрины Geely в ОАЭ, — что и есть самый сильный сигнал, что форма была правильной. Хорошая архитектура — та, которую хочется переиспользовать.

Что выдержало:

  • Дисциплина единого источника истины. Один каталог в Medusa, всё остальное читает его. На любой вопрос «откуда взялась эта цифра» был ровно один ответ.
  • Локализация на уровне полей вместо документов под каждый язык. Команда вообще о ней не думает — а это и есть цель.
  • Выбор рендеринга на уровне маршрута. Статическая маркетинговая оболочка, динамические цены. Быстро там, где можно, корректно там, где нужно.

Что я сделал бы иначе:

  • Мы строили на Next.js 15. Next.js 16 — текущая стабильная линия по состоянию на середину 2026 года, с App Router по умолчанию и React Compiler, который теперь стабилен (хотя всё ещё включается опционально, а не по умолчанию). На новом проекте сегодня я бы стартовал на 16 и включил компилятор, чтобы он взял на себя мемоизацию, которую мы частично делали руками. Путь обновления — реальная работа, но её стоит запланировать, а не откладывать бесконечно.
  • Более ранние вложения в превью-окружения. Мы добавили надёжные превью на каждую ветку после того, как пара циклов ревью со стейкхолдерами стала шумной. На Geely мы настроили это в первый же день, и ревью стало заметно спокойнее.
  • Более тонкий слой состояния конфигуратора. Наша первая версия держала на клиенте больше производного состояния, чем было нужно. Более чистая версия вычисляет меньше и больше спрашивает у коммерческого слоя — меньше мест, где фронтенд и каталог могут разойтись.

Если из этой headless-архитектуры коммерции и есть один переносимый урок, то он в том, что сложные решения — не про то, какой фреймворк. Они про то, где живёт истина, кто что может менять без деплоя и смогут ли люди, наследующие систему, действительно ею управлять. Сделайте эти три вещи правильно — и Next.js, Sanity и Medusa станут просто деталями, которые делают её быстрой.

Частые вопросы

Что такое headless-архитектура электронной коммерции?

Headless-архитектура электронной коммерции отделяет витрину (то, что видит покупатель) от коммерческого движка (каталог, цены, заказы) и от системы контента (маркетинговые тексты, кампании). Они общаются по API. На NIO это означало фронтенд на Next.js, который читает коммерцию из MedusaJS, а контент — из Sanity, причём каждым слоем можно владеть и менять его отдельно, так что обновление цен или правка текста ни разу не требовали передеплоя остальных.

Почему для такого проекта использовать MedusaJS, а не Shopify?

MedusaJS — open-source и полностью ваша собственность, что важно, когда клиенту нужно владеть сложным каталогом с региональными ценами от и до, без посемейной SaaS-тарификации. Shopify с Hydrogen быстрее поднять для стандартных розничных каталогов. Честное дерево решений сводится к сложности каталога, владению и бюджету — я разбираю это в [Hydrogen против Medusa](/insights/hydrogen-vs-medusa).

Может ли нетехническая команда маркетинга действительно вести Sanity CMS сама?

Да, но только если модель контента спроектирована вокруг того, что они редактируют, а не вокруг вёрстки страницы, и только если у них есть визуальные превью. Команда на NIO публикует ежедневно на двух языках без участия разработчиков. Провальный сценарий — моделировать контент так, чтобы он зеркалил ваши компоненты: это рождает CMS, которой редакторы избегают. Локализация на уровне полей и превью с правкой по клику — вот что делает это устойчивым.

Подходит ли Next.js для конфигуратора автомобиля?

Да. Конфигуратор — это интерактивное состояние клиента (выборы), наложенное на истину, полученную с сервера (цены, наличие). Next.js с React позволяет держать интерактивное состояние на клиенте, резолвя всё ценочувствительное на сервере на каждый запрос, так что конфигуратор никогда не покажет тихо устаревшую цену. Именно сборка его кастомно, а не использование готового виджета, позволяет ему соответствовать бренду и правилам ценообразования региона.

Каково текущее состояние Next.js для такого проекта в 2026 году?

По состоянию на середину 2026 года текущая стабильная линия — Next.js 16, App Router идёт по умолчанию, а React Compiler стабилен (опционален, пока не включён по умолчанию). Платформа NIO вышла на Next.js 15; на новом проекте сегодня я бы стартовал на 16 и включил компилятор, чтобы мемоизация делалась автоматически. Архитектурные паттерны — серверные компоненты, выбор рендеринга на уровне маршрута — переносятся между двумя версиями без проблем.

Как держать витрину и коммерческие данные в синхронизации без ревалидационного хаоса?

За счёт того, что не относиться к витрине как к кэшу, который инвалидируется вручную. Ценочувствительные данные (цены, наличие) подтягиваются на сервере в момент запроса, поэтому они всегда актуальны; предгенерируется только по-настоящему статическая маркетинговая оболочка. Это избавляет от кладбища ревалидационных вебхуков, которое накапливают headless-проекты, и означает, что каталог в Medusa остаётся тем единственным источником истины, о синхронизации которого никому не нужно помнить.

Есть похожая задача?

Начать проект