Skip to main content

Vercel Microfrontends with SitecoreAI : Split a Multi-Site SitecoreAI Platform Across Apps

The Problem

Let's say your company has one website for the whole world, but inside it there are actually three separate divisions. Each division has its own team, its own products, and its own pages on the same domain. Something like:

www.yourcompany.com
├── /clothing/*     → the clothing division
├── /sanitation/*   → the sanitation division
└── /healthcare/*   → the healthcare division

Now think about how a team would usually build this. One big application for the whole site. Every division team commits into the same codebase, and every small change rebuilds and redeploys the entire site. Fast teams slow down, and the build waits become painful.

There is another way to build it, and this tutorial will walk you through it step by step: Vercel Microfrontends, working with a Sitecore AI (headless) setup. Instead of one big app, you build several small apps. Vercel joins them into one website for the visitor.

What you will learn

  • How to split one domain into several separate applications
  • How to tell Vercel which app owns which paths
  • Three common mistakes that confuse beginners (including one that caused us a real bug)
  • How to test the routing before it reaches users

You don't need to know every detail of Next.js or Sitecore. You just need the basics of how a website uses URLs and folders. Let's go.

Step 1 — Understand the idea

Think of a big office building with a receptionist. Visitors come in and say "I'm here for the clothing division" or "I'm here for healthcare". The receptionist sends them to the right floor. Each floor runs itself — different teams, different managers — but the building looks joined together from outside.

That is exactly the microfrontend idea. Vercel is the receptionist. The "floors" are separate apps. When a visitor requests a URL, Vercel looks at the path and sends that request to whichever app owns it.

Request: /de-DE/bekleidung/products
         ↓
Vercel looks at the path,  decides it belongs to the
"clothing" app, and forwards the request there.

There is always one main app (also called the default or shell app). It owns the normal paths like the homepage and the company/corporate pages. All other apps are child apps. Each child claims specific paths. If no child claims a path, the request goes to the main app.




Step 2 — Declare the apps in a config file

Everything starts with one JSON file called microfrontends.json. It lives in the main app. This file is simply the list of "which app owns which paths".

{
  "applications": {
    "main-site": {
      "development": { "fallback": "https://preview.main-site.com" }
    },
    "clothing": {
      "development": { "fallback": "https://preview.clothing.com" },
      "routing": [
        { "paths": ["/en/clothing/:path*", "/de-DE/bekleidung/:path*", "/fr-FR/vetements/:path*"] }
      ]
    },
    "sanitation": {
      "development": { "fallback": "https://preview.sanitation.com" },
      "routing": [
        { "paths": ["/en/sanitation/:path*", "/de-DE/sanitaer/:path*", "/pl-PL/sanitacja/:path*"] }
      ]
    },
    "healthcare": {
      "development": { "fallback": "https://preview.healthcare.com" },
      "routing": [
        { "paths": ["/healthcare", "/healthcare/:path*", "/de-DE/medizin/:path*"] }
      ]
    }
  }
}

Let me explain the pieces in plain words:

  • fallback — the address of the app when it is running. During local development you use this to tell Vercel where each app lives.
  • paths — the list of URL paths this app handles. The :path* part just means "and anything below this folder". So /en/clothing/:path* means "all pages under /en/clothing".
  • Notice the paths are translated. The same clothing division is /en/clothing in English, /de-DE/bekleidung in German and /fr-FR/vetements in French. This matters a lot for search engines — every language gets a clean, real URL. (And yes, these words are just real translations: bekleidung and vetements both mean clothing, sanitaer means sanitation in German, sanitacja means sanitation in Polish, and medizin means medicine.)

If the Vercel project name is different from the app name in your code, you can link them with "packageName". In the real project we also kept separate copies of this file for staging and production, because each environment had different project names.

Step 3 — Switch it on in the app

Next, each app needs one small change in its next.config.ts file. We use the official helper that Vercel provides:

import { withMicrofrontends } from '@vercel/microfrontends/next/config';

const nextConfig = {
  // your normal Sitecore setup goes here
};

// Turn it on only for Stage and Production, not on your laptop.
const useMicrofrontends = process.env.ENABLE_MICROFRONTENDS === 'true';
export default useMicrofrontends ? withMicrofrontends(nextConfig) : nextConfig;

Why the ENABLE_MICROFRONTENDS switch? Because with the helper turned on, the sites do not open normally on your local machine. So we keep it off for local development and turn it on in Stage and Production by setting an environment variable there. This switch also becomes a handy trouble-shooting tool: if something behaves strangely on Stage, switch it off and see if the problem is related to microfrontends at all.

The helper does one more important thing, and beginners often miss it. Each app loads its own CSS and JavaScript files. Without any change, every app would give its files the same name (like /_next/static/main.js). On one shared website, those files would clash and pages would look broken. The helper solves this by giving every app its own unique file prefix, so no two apps ever overwrite each other's files. If you ever see a page with broken styles, the first thing to check is that this helper is enabled on that app.

Step 4 — Big change: each app only sees its own requests

In a single big app, every request goes through that one app's logic. Developers are used to that. With microfrontends, this changes completely, and it's the thing that surprises almost everyone.

Vercel decides where a request goes before any of your code runs. So the code inside each app only ever sees the requests that were sent to that specific app. Your main app can never run logic for a child app's page, and the child apps can never run logic for the main site.

Request for /de-DE/bekleidung/products
  1. Vercel decides: this path belongs to the clothing app
  2. The request is sent to the clothing app
  3. Only the clothing app's code runs

What does this mean in practice? Consider redirects, for example — the small rules that move visitors from an old URL to a new one. The simple rule is: a redirect only works if it is defined in the app that receives the request. If a visitor hits /de-DE/bekleidung and that path is owned by the clothing app, then a redirect defined in the main app will never fire. It has to be defined in the clothing app. If a redirect seems to "disappear", this is almost always the reason.

Step 5 — A bug we actually hit: one small character

Let me show you a real bug from our project, because it teaches the most important rule in this tutorial.

Vercel path patterns have two very similar endings:

  • :path* — zero or more extra segments ("everything from this folder down")
  • :path+ — one or more extra segments

We had claimed the sanitation section with /sanitation/:path*, which also claims the bare path /sanitation with nothing after it. That seems harmless. But consider the bare /sanitation page. We wanted our main app to handle it — specifically, to look at the visitor's country and redirect them to /de-DE/sanitaer, /pl-PL/sanitacja or the right localized page for their country.

Because we had claimed every /sanitation path on the child app, the request went straight to the child — and the child never reached our country-based redirect logic. Every visitor, from every country, got sent to the same default language page. The main app's logic never ran, because the main app never received the request.

The fix is a change of one character. Let the child own only the subpages, and leave the bare path for the main app:

"routing": [
  { "paths": ["/sanitation/:path+"] }
]

Now /sanitation stays with the main app (which does the country redirect), and /sanitation/products and deeper pages go to the child.

Boring but valuable lesson: be careful about claiming a root path on a child app. If a route's bare path has special behavior in the main app, don't let a child own it.

Step 6 — Make the computer check your config

With many translated paths, mistakes are easy to make. Someone adds /pl-PL/sanitacja in the config file but forgets to tell the app, and suddenly Polish visitors land on the wrong site. We solved this by writing a small script that checks the config automatically. The Vercel package even gives us a helper for it:

import { validateRouting } from '@vercel/microfrontends/next/testing';

// "Given these URLs, ask Vercel's own logic: which app gets them?"
validateRouting('microfrontends.json', {
  'clothing':    ['/en/clothing', '/de-DE/bekleidung/products'],
  'sanitation':  ['/sanitation/products', '/pl-PL/sanitacja'],
  'main-site':   ['/', '/sanitation', '/about-us'],
});

This test asks Vercel's own rules, "if a visitor opens this URL, which app handles it?" — and fails if the answer is different from what we expect. We run it in our build pipeline, so a wrong path pattern stops the delivery before it reaches real visitors. If you take one idea from this post, make it this one.

Step 7 — Run it locally and deploy

For local development, Vercel's package includes a small program that brings all your apps together on your computer, so you can test the routing the same way it will work online. You tell it which port to use:

"options": { "localProxyPort": 3024 }

Then you run all the apps (each on its own port) and open localhost:3024. The program forwards each path to the right app, exactly like Vercel will later:

localhost:3024
├── /                        → main app
├── /de-DE/bekleidung/*      → clothing app
└── /pl-PL/sanitacja/*       → sanitation app

On Vercel itself, each app is a separate project, and the projects are grouped together with the main app set as default. Here's the nice part — fallback. If you only change the clothing app and deploy, then in the preview URL the clothing pages show your new change, while sanitation and healthcare automatically keep using their older, still-working versions. No one is blocked waiting for the other teams. Each team can deploy its own app whenever it wants.

Summary — should you use this?

Microfrontends are not magic. They add some complexity: the apps can no longer share code by importing directly from each other, redirect logic has to live in the right app, and deployment has more moving parts. So use them when they solve a real problem.

For us, the problem was clear: three separate business divisions, three teams, one domain, and one site that kept slowing everyone down. Microfrontends split that problem neatly, and Vercel handled all the tricky joining-up part for us.

If this tutorial made you curious, Vercel has a full, very practical course at Vercel Academy — Microfrontends on Vercel. It covers the decision framework, a monorepo setup, testing and more, all with real code.

Hope this helps you!

Comments

POPULAR POSTS

Sitecore PowerShell Script to create all language versions for an item from en version

  We have lots of media items and our business wants to copy the data from en version of media item to all other language versions defined in System/Languages. This ensures that media is available in all the languages. So, we created the below powershell script to achieve the same -  #Get all language versions defined in System/Languages $languages = Get-ChildItem /sitecore/System/Languages -recurse | Select $_.name | Where-Object {$_.name -ne "en"} | Select Name #Ensuring correct items are updated by comparing the template ID  $items = Get-ChildItem -Path "/sitecore/media library/MyProjects" -Recurse | Where-Object {'<media item template id>' -contains $_.TemplateID} #Bulk update context to improve performance New-UsingBlock (New-Object Sitecore.Data.BulkUpdateContext) { foreach($item in $items){    foreach($language in $languages){ $languageVersion = Get-Item -Path $item.Paths.Path -Language $language.Name #Check if language versi...

Export Sitecore media library files to zip using SPE

If you ever require to export Sitecore media files to zip (may be to optimize them), SPE (Sitecore Powershell Extension) has probably the easiest way to do this for you. It's as easy as the below 3 steps -  1. Right click on your folder (icons folder in snap)>Click on Scripts> Click on Download 2. SPE will start zipping all the media files placed within this folder. 3. Once zipping is done, you will see the Download option in the next screen. Click Download Zip containing the media files within is available on your local machine. You can play around with the images now. Hope this helps!! Like and Share ;)

Make Sitecore instance faster using Roslyn Compiler

When we install the Sitecore instance on local, the first load is slow. After each code deploy also, it takes a while for the Sitecore instance to load and experience editor to come up. For us, the load time for Sitecore instance on local machines was around 4 minutes. We started looking for ways to minimize it and found that if we update our Web.config to use Roslyn compiler and include the relevant Nugets into the project, our load times will improve. We followed the simple steps - Go to the Project you wish to add the NuGet package and right click the project and click 'Manage NuGet Packages'. Make sure your 'Package Source' is set to nuget.org and go to the 'Browse' Tab and search Microsoft.CodeDom.Providers.DotNetCompilerPlatform. Install whichever version you desire, make sure you note which version you installed. You can learn more about it  here . After installation, deploy your project, make sure the Microsoft.CodeDom.Providers.DotNetCompilerPlatform.d...

Experience of a first time Sitecore MVP

The Journey I have been working in Sitecore for almost 10 years now. When I was a beginner in Sitecore, I was highly impressed by the incredible community support. In fact, my initial Sitecore learning path was entirely based on community written blogs on Sitecore. During a discussion with my then technology lead Neeraj Gulia , he proposed the idea that I should start giving back to developer community whenever I get chance. Just like I have been helped by many developers via online blogs, stackoverflow etc., I should also try to help others. Fast forward a few years and I met  Nehemiah Jeyakumar  (now an MVP). He had a big archive of his technical notes in the form Sitecore blogs. I realized my first blog dont have to be perfect and it can be as simple as notes to a specific problem for reference in future. That's when I probably created my first blog post on Sitecore. At that time, I didn't knew about the Sitecore MVP program. Over the years, I gained more confidence to writ...

Clean Coding Principles in CSharp

A code shall be easy to read and understand. In this post, I am outlining basic principles  about clean coding after researching through expert recommended books, trainings and based on my experience. A common example to start with is a variable declaration like - int i  The above statement did not clarify the purpose of variable i. However,  the same variable can be declared as -  int pageNumber The moment we declared the variable as int pageNumber, our brain realized that the variable is going to store the value for number of pages. We have set the context in our brain now and it is ready to understand what the code is going to do next with these page numbers. This is one of the basic advantages of clean coding. Reasons for clean coding -  • Reading clean code is easier - Every code is revisited after certain amount of time either by the same or different developer who created it. In both the cases, if the code is unclean, its difficult to understand and u...