Architecture & frontend
If you are searching for split frontend, or for when microfrontends, you are usually here: the app got large, the merge queue is long, and someone has said you 'need to split the monolith'. Someone creates three Next.js apps and a shared package monorepo and calls it done. Someone cuts the marketing site from the app and discovers the header is now two truths. Someone buys a microfrontend runtime because that is what the big companies have. Someone refuses to touch anything until everything is one build — including the part that ships every week and the part that ships twice a year.
The design-system post is about tokens and who owns components. The legacy post is about when a whole system should be replaced. This one is about the frontend app as a deployable: one repo, one build, one hostname — or several. An honest split is not a diagram with arrows. It is two cadences you can point at, or two blast radii you no longer want to share. Without that sentence you have more folders. You do not have more products.
TL;DR
The main points
- A frontend monolith is one deployable. It is not an insult. Most Next.js sites should stay there.
- Split when cadence, ownership or blast radius are actually two jobs — not when folder density feels embarrassing.
- Microfrontends are often an org solution you buy as architecture. If you do not have two teams, you do not have that problem.
- A shared design system or package does not require two apps. It requires one place that owns the token.
- A split without module boundaries first is a distributed monolith: you still have one release, just with a network in between.
- Do not call two hostnames a split if you still merge in the same queue and ship them the same evening.
Why this matters in the first place
A split you cannot explain costs weeks you call 'independence'. People create a second app directory because the merge queue hurts, and discover they still wait on the shared layout package, the shared auth, the shared feature flag, and that the header now diverges. You think you have two products. You have two places you must remember to patch. The ritual is expensive because it gives you permission not to clean the monolith you actually have.
Teams inherit the word microfrontends from a different kind of software. In a bank with twenty frontend teams and one legacy shell, runtime composition is survival. On a Next.js site with one product team and a marketing site it is often theatre: you pay to keep two builds, two preview environments, two sets of environment variables and a contract between them that nobody owns. The monolith solved being able to see the whole page. The split should solve something the monolith cannot.
It is also a trust and SEO question. If the marketing site and the app split, and you forget redirects, the sitemap and the shared footer, you have two sites Google treats as halves. If you split 'for performance' without measuring what was actually heavy, you have moved JS from one bundle into two downloads plus an orchestration. If nobody owns the seam, it diverges — and then the green 'we are decoupled' is a lie you write on an architecture slide.
We write this because 'we should split the frontend' has become a purchase you make before you have a sentence about which queue, which team or which radius you want to separate. An honest setup is boring: one Next.js repo for as long as cadence is the same, clear folders when the code is large, and two apps only when you can point at the job the other build must hold alone. Without that sentence you have an extra package.json. You do not have an architecture.
What a frontend monolith actually is — and what a split is not
A frontend monolith is one deployable you roll out as one truth: the same build, the same release, the same hostname or the same app shell. It is allowed to be large. It is allowed to have folders for marketing, app, admin. It is a monolith because you cannot send marketing live without also sending the code you did not touch. That is a trait, not a diagnosis. Most sites we see should be exactly that.
It is not 'we have a lot of files'. A large App Router tree with clear routes is still one product. Module boundaries — `app/(marketing)`, `app/(app)`, a `ui` package you own — are housekeeping. They are not a split. If you can change the homepage without reading the billing code, you already have what people think they buy with a second repo. You just have not paid the network for it.
A split is two (or more) deployables with their own cadence or their own blast radius. Marketing on Vercel that can go live when copy is ready. The app that waits on a migration. An admin you do not want in the public bundle's attack surface. That needs a seam you can point at: a hostname, an auth cookie domain, a package contract, a redirect list. Without the seam it is two folders you pretend are independent.
And it is not microfrontends as the default. Runtime composition — web components in a shell, module federation, an iframe hotel — is a tool for when two teams must ship to the same screen without sharing a build. If you are one team, it is an extra runtime you will debug at three in the morning. Build boundaries in the code first. Split the deploy when the queue or the radius forces you. Not the other way around.
What typically goes wrong when someone says we have split the frontend
The first failure is splitting on files, not on jobs. You extract `app/blog` because the folder is large, and discover the blog still shares auth, i18n, preview and the same release manager. You moved code. You did not move cadence. An honest split starts with the sentence 'this part must be able to go live on Wednesday even if the app is frozen'. Without that sentence the extract is interior design.
The second failure is the shared thing you did not own. Header, footer, cookie banner, feature flags, the chat gate. In two apps that becomes two truths in three weeks unless one package has an owner and a release. The design-system post is the right place for tokens. Here the duty is simpler: if you split, write what is still one, and who may change it. Otherwise you get two homepages you call brand.
The third failure is SEO and the URL contract. Marketing on `www`, app on `app.`, without redirects, without one sitemap strategy, without deciding which hostname owns `/pricing` and `/login`. Google sees two sites. The user sees a footer that points the wrong way. The relaunch post is about cutover. Here you die in the small: you split the deploy and forgot you still have one web job in front of the index.
The fourth failure is buying runtime before you have teams. Module federation, a shell, an iframe around checkout. That can be right when two organisations must meet on one page. It is wrong when five developers want to 'be ready to scale'. You get the expensive habit of debugging two builds and a load-order problem you did not have when it was one `next build`. Keep it boring until the queue has a name.
One app vs. an honest split — two deployables, two kinds of truth
The honest frontend monolith
One build, one release, clear folders. Cadence is the same. You can see the whole page.
- One preview per pull request — review sees marketing and app together
- One header, one cookie banner, one set of redirects you actually own
- Catches the bugs that only exist when the pages share layout and auth
- Is the right default until you can name the job one build cannot do
The honest split
Two deployables with their own cadence or radius — and a seam you can point at.
- Marketing can go live while the app is frozen — or the other way around
- An owned shared layer (package or design token), not copy-paste in two repos
- URL and hostname contract: what lives where, what 301s, what is noindex
- Is waste if the only pain was that the `app/` folder felt large
How to decide whether you should split at all
Without this order you get an extra repo you call independence.
- 01
Write the job the monolith cannot hold — in five lines
Not 'the app got large'. 'Marketing must be able to ship copy on Wednesday while the app is in a feature freeze.' 'Admin must not live in the public bundle.' 'Two teams merge in the same queue, and one blocks the other every week.' If you cannot say the job, you are not ready for a split. You are ready to tidy the folders you already have.
- 02
Check whether module boundaries already do the job
Route groups, a `ui` package, a ban on importing billing from the blog. That is what most 'large' Next.js apps are missing. If the pain is finding files, or a junior importing across, that is lint and folders. Another hostname does not fix an import mess. The design system fixes tokens. It does not fix cadence.
- 03
List what must still be one after a split
Auth cookie. Brand header. Cookie banner. i18n. Feature flags. Sitemap and redirects. If the list is long and you have no owner for it, the split will turn the list into two lies. Write the owner. Write the release of the shared thing. If you will not own the shared thing, stay in the monolith. That is more honest.
- 04
Decide hostname, URL and blast radius — not just the folder
The same hostname with a rewrite is still one site for Google and for the cookie. Two hostnames are two sites. A split 'for security' means admin does not share a bundle with marketing — that is radius. A split 'for SEO' without a redirect list is a hole. The relaunch post is the checklist if you move paths. Here you just must not pretend a new `apps/` directory is a cutover.
- 05
Give the split a kill criterion — or do not turn it on
If the job was 'two teams, two queues' and you become one team again, put it back together. If the job was 'marketing frozen out of the app release' and you now deploy both every day, two builds are a subscription to operations. A split without a kill date is how you pay for a shell after the queue died. Write when you merge it again.
“You have split the frontend when two cadences can go live without waiting. An extra repo is a hotel.”
Marketing site, app and admin — the same word, three jobs
A Next.js marketing site plus an app in the same repo is what we typically recommend for as long as it is one team and copy does not have to go through the app's freeze. The pull-request preview shows the whole truth. The header is one. The cookie banner is one. When marketing starts waiting three weeks on an app release to fix a homepage, you have the job. Then two apps are right — with an owned package for what must still look the same, and a redirect list so `/` and `/login` do not become a guess.
WordPress marketing plus a Next.js app is already a split you may not have named. Two CMSes, two deploys, two footers. The failure here is rarely that you 'lack microfrontends'. The failure is pretending it is one brand without owning the shared thing, and having two sitemaps fighting over the same queries. Either move marketing into Next.js, or write the contract: which paths WordPress owns, which the app owns, and what 301s. The WordPress-to-Next post is the cutover. This one is the decision about whether they should stay two at all.
An admin next to the public site is the split that is most often right for radius, not ego. Admin with internal tools, webhooks, a different auth basis, does not belong in the bundle you send to the homepage. It can still be the same monorepo. It should not be the same public JavaScript. If you 'split' by making an `/admin` route in the same app without separating auth and bundle, you moved the URL. You did not move the radius.
Microfrontends in one shell are the last job, not the first. When two organisations must ship to the same screen — a checkout from one team, a catalogue from another — and you cannot share a build, runtime composition is a tool. Until then it is a framework you debug instead of the product. Build the monolith honestly. Split the deploy when the queue has a name. Do not buy a shell because the slide had arrows.
What you should typically do in the situation you are in
The left is what you typically say. The right is the next step we typically recommend.
Situation
The app folder feels large, and someone said microfrontends
Next step
Write the job one build cannot do. If you cannot, tidy folders and imports — not hostnames
Situation
Marketing waits on the app freeze to fix a homepage
Next step
Then you have the cadence job. Two apps, owned shared layer, URL contract — not an iframe hotel
Situation
We have two repos and still merge in the same release week
Next step
You have not split. Put it back together, or give one a cadence the other does not own
Situation
Admin lives in the public bundle 'so it is easy'
Next step
That is radius, not convenience. Separate auth and the JS you send to the homepage
Situation
We want to share a header without sharing a build
Next step
One package with an owner. Two copy-paste headers are two brands. Runtime composition is last resort
Checklist before you call it a frontend split
If you are missing several of these, the sentence is independence — the setup is an extra package.json.
- You can say which job the other deployable holds that the monolith cannot
- Cadence or blast radius is actually two — not just folder density
- The shared thing (header, auth, banner, flags) has an owner and a release
- Hostname, paths and redirects are written before you move the first route
- Two teams can roll out without booking the same evening — or you are honest that you are still one team
- You know when you put it back together if the job disappears
Questions we get again and again
When should we split a frontend monolith?
When cadence, ownership or blast radius are actually two jobs: marketing must be able to go live while the app is frozen; two teams block each other in the same queue every week; admin must not share a bundle with the homepage. If the job is that the folder feels large, or that the word microfrontends sat on a slide, tidy routes and imports. Most Next.js sites have the latter job, not the former. Write the sentence. If you cannot, do not buy the second hostname.
Are microfrontends the same as splitting the frontend?
No. A split is two deployables. Microfrontends are one way to compose them on the same screen at runtime. You can split marketing and app on two hostnames with no federation. You can also have module federation and still be one release if you merge in the same queue. We see the latter more often than the former. Buy runtime when two organisations must meet on one page. Until then, two boring apps — or one — is the honest tool.
Can we have one monorepo and still call it a split?
Yes, if there are two builds you can roll independently, and a shared layer with an owner. The monorepo is interior design. The split is the release. An `apps/web` and `apps/app` that always deploy the same evening are a monolith with extra tooling. A monorepo where marketing goes live on Wednesday and the app waits on a migration is a split. Call it what it is. Folders lie less than slides.
Should the marketing site and the app be two hostnames?
Only if you want two cadences or two indexing surfaces, and you will own the contract. The same hostname with rewrites is still one site for cookies and for Google — often the right call when the brand should feel like one. Two hostnames are right when you deliberately separate app auth from the public site, or when the two must not share a freeze. Without a redirect list and without deciding who owns `/` and `/login`, two hostnames are a hole, not an architecture.
What do we do with a split that still waits on the shared package?
Then you have not split cadence. You have split the folder. Either give the package a release the other app can ignore for a week, or put it back together. A shared package without semver and without an owner is the old monolith with a `workspace:` prefix. That is fine as a step. It is not fine as the sentence you tell leadership.
If you have two repos and still book the same release week
Let us separate cadence, radius and folders.
A named job beats a shell you create because the monolith felt embarrassing.
