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/clothingin English,/de-DE/bekleidungin German and/fr-FR/vetementsin French. This matters a lot for search engines — every language gets a clean, real URL. (And yes, these words are just real translations:bekleidungandvetementsboth mean clothing,sanitaermeans sanitation in German,sanitacjameans sanitation in Polish, andmedizinmeans 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
Post a Comment