~/oybek.dev
Book a call
insights
ArchitectureJune 1, 2026· 10 min read

How we shipped NIO's regional EV platform — the architecture calls that mattered

A case-study deep dive into NIO's regional EV platform: the custom configurator, MedusaJS product sync, showroom scheduling and a self-serve Sanity CMS — the headless architecture calls that mattered, and what I'd change.

When a global EV brand asked us to launch its regional platform across MENA, the brief wasn't "build a website." It was a configurator, a commerce backbone, a showroom booking flow, and a content system the regional marketing team could run without ever filing a developer ticket — all live by a launch date that wasn't moving. The interesting part of NIO's regional EV platform isn't the feature list. It's the headless ecommerce architecture underneath it: how a Next.js front end, a Sanity CMS and a MedusaJS commerce core were wired together so that four separate systems read like one product to the buyer and stayed cheap to change for the team. This is the deep dive — the calls that mattered, what held up under real regional traffic, and what I'd do differently.

High-level architecture diagram showing a Next.js frontend connected to Sanity, MedusaJS and a booking service
The four systems behind one storefront

The constraint that shaped every architecture decision

NIO came with the constraints you'd expect from a global automotive brand: strict brand standards, a queue of stakeholders, and a hard deadline. But the constraint that actually drove the architecture was the boring one — handover. Whatever we built had to be runnable by a regional marketing team with no engineers on staff, and it had to keep being runnable a year later when we were off the project.

That single requirement killed a lot of tempting shortcuts. Content couldn't live in code. Pricing couldn't be a hardcoded JSON file someone would forget existed. And the commerce layer had to be something we owned and could hand over, not a SaaS account with a vendor's roadmap attached. If you've read why I lean on headless setups for real commerce work, this is the whole argument in one project.

Why we built a custom configurator instead of an off-the-shelf one

The vehicle configurator was the obvious place to reach for a third-party widget. We didn't, and I'd make that call again every time.

Off-the-shelf automotive configurators optimise for the dealer's catalogue, not the brand's experience. They come with their own DOM, their own styling escape hatches, and their own idea of how pricing works — which never matches a region where duties, options bundles and currency all differ from the global default. The moment you need a configurator to honour MENA-specific pricing rules and still feel like NIO rather than a generic plugin, the "save time" widget becomes the thing you fight for three sprints.

So we built it as a React state machine over the model, trim, colour and options tree, with live pricing resolved against the commerce layer rather than baked into the front end. The configurator's job is to render choices and compute a valid selection; the *price* of that selection is always the commerce layer's answer. That separation is what let pricing change without a deploy — and it's why a regional pricing update never touched front-end code.

The vehicle configurator UI mid-selection, trim and colour panel open with a live price
Configurator state on the client, pricing from Medusa

MedusaJS as the source of truth, not a checkout button

People assume "headless commerce" means a checkout SDK bolted onto a marketing site. On NIO it was the opposite: MedusaJS was the spine, and the storefront was a read view of it.

Vehicles, accessories, trims, regional pricing and availability all live in Medusa as the single source of truth. The Next.js app reads from it, the configurator validates against it, and the showroom flow attributes leads against the same catalogue. We picked Medusa specifically because it's open-source and fully ownable — no per-seat SaaS pricing, no catalogue we'd have to migrate off when the contract changed, and a module system we could extend for the parts of automotive commerce that no platform ships out of the box. I've written the full Hydrogen vs Medusa decision tree separately, but the short version for NIO: a complex, regionally-priced catalogue the client needs to own end-to-end points at Medusa, not Shopify Hydrogen.

The sync between Medusa and the storefront is where headless architectures usually rot. The mistake is treating the front end as a cache that has to be manually invalidated — you end up with a graveyard of revalidation webhooks nobody trusts. We kept the read path simple: the storefront fetches catalogue and pricing data through typed server-side calls at request time for anything price-sensitive, and only statically generates the genuinely static marketing shell. Wrong pricing on a car is worse than a slightly slower page, and server components let you make that trade-off per route instead of for the whole app.

A CMS the marketing team actually runs

The reason Sanity is in this stack and not a heavier traditional CMS is the handover requirement again. The MENA team publishes campaigns, model-page updates and news daily, in two languages, without a developer in the loop. That only works if the content model is shaped around *what the team edits*, not around the page layout.

So we modelled structured content types — campaign, model page, news entry — with field-level localisation rather than duplicated documents per language. An editor changes a headline once and edits its translation right beside it; the front end resolves the requested locale with an English fallback. The lesson I keep relearning, and wrote up in Sanity in the real world, is that the difference between a CMS demo and a CMS still in daily use a year later is almost entirely content modelling and previews. We gave them real visual editing — click a headline on the live preview, edit it in place — because a marketing team that can see its change before publishing actually publishes.

The test of a CMS isn't the launch demo. It's whether anyone's still editing in it twelve months later without calling you.
Sanity Studio open beside a live preview of a NIO model page, editor mid-edit
Field-level localisation and click-to-edit visual editing

Showroom scheduling: the unglamorous system that earned its keep

The booking flow — test drives and showroom visits — looked like the smallest piece and turned out to be the one the business cared about most, because it's where the platform produces leads. We wired bookings into the lead pipeline with full-funnel tracking, so marketing could see the whole journey from configurator interaction to booked visit, not a form submission floating in isolation.

None of this is technically hard. The architectural call that mattered was keeping the booking service its own bounded concern that *references* the catalogue rather than re-implementing it, so a model going out of stock in Medusa is reflected in what you can book a visit for, with zero duplicated data.

What held up — and what I'd do differently

The platform launched on schedule and has been in production since, with the configurator, bookings and commerce sync handling real buyers across the region. The same architecture became the foundation for our next regional automotive build, the Geely UAE storefront — which is the strongest signal that the shape was right. A good architecture is one you want to reuse.

What held up:

  • The source-of-truth discipline. One catalogue in Medusa, everything else reads it. Every "where does this number come from" question had exactly one answer.
  • Field-level localisation over per-language documents. The team never thinks about it, which is the goal.
  • Per-route rendering choices. Static marketing shell, dynamic pricing. Fast where it can be, correct where it must be.

What I'd do differently:

  • We built on Next.js 15. Next.js 16 is the current stable line as of mid-2026, with the App Router as the default and the React Compiler now stable (though still opt-in, not on by default). On a greenfield build today I'd start on 16 and turn the compiler on to handle memoisation we did partly by hand. The upgrade path is real work but worth scheduling, not deferring indefinitely.
  • Earlier investment in preview environments. We added robust per-branch previews after a couple of stakeholder review cycles got noisy. On Geely we set that up on day one and review got dramatically calmer.
  • A thinner configurator state layer. Our first version held more derived state on the client than it needed. The cleaner version computes less and asks the commerce layer more — fewer places for the front end and the catalogue to disagree.

If there's one transferable lesson from this headless ecommerce architecture, it's that the hard decisions aren't about which framework. They're about where the truth lives, who can change what without a deploy, and whether the people inheriting the system can actually run it. Get those three right and Next.js, Sanity and Medusa are just the parts that make it fast.

FAQ

What is a headless ecommerce architecture?

A headless ecommerce architecture separates the storefront (what the customer sees) from the commerce engine (catalogue, pricing, orders) and the content system (marketing copy, campaigns). They communicate over APIs. On NIO, that meant a Next.js front end reading from MedusaJS for commerce and Sanity for content — each layer ownable and changeable on its own, so a pricing update or a copy change never required redeploying the others.

Why use MedusaJS instead of Shopify for a project like this?

MedusaJS is open-source and fully ownable, which matters when the client needs to own a complex, regionally-priced catalogue end-to-end with no per-seat SaaS pricing. Shopify with Hydrogen is faster to stand up for standard retail catalogues. The honest decision tree comes down to catalogue complexity, ownership and budget — I break it down in [Hydrogen vs Medusa](/insights/hydrogen-vs-medusa).

Can a non-technical marketing team really run a Sanity CMS alone?

Yes, but only if the content model is designed around what they edit rather than around the page layout, and only if they have visual previews. The team on NIO publishes daily in two languages without developer involvement. The failure mode is modelling content to mirror your components — that produces a CMS editors avoid. Field-level localisation and click-to-edit previews are what make it stick.

Is Next.js a good choice for a vehicle configurator?

Yes. A configurator is interactive client state (selections) layered over server-fetched truth (pricing, availability). Next.js with React lets you keep the interactive state on the client while resolving anything price-sensitive server-side per request, so the configurator can never quietly show a stale price. Building it custom rather than using an off-the-shelf widget is what lets it match the brand and the region's pricing rules.

What's the current state of Next.js for a build like this in 2026?

As of mid-2026 the current stable line is Next.js 16, the App Router is the default, and the React Compiler is stable (opt-in, not yet on by default). The NIO platform shipped on Next.js 15; on a greenfield build today I'd start on 16 and enable the compiler to handle memoisation automatically. The architectural patterns — server components, per-route rendering choices — carry over cleanly between the two.

How do you keep the storefront and commerce data in sync without revalidation chaos?

By not treating the storefront as a manually-invalidated cache. Price-sensitive data (pricing, availability) is fetched server-side at request time, so it's always current; only the genuinely static marketing shell is pre-generated. That avoids the graveyard of revalidation webhooks that headless builds accumulate, and it means the catalogue in Medusa stays the one source of truth nobody has to remember to sync.

Have something like this in mind?

Start a project