Micro-frontend performance: the costs and how to control them
Splitting a frontend into independently deployed parts adds requests, bytes and startup work. None of it is fatal. It has to be measured and managed like any other budget.
Where the cost comes from
A single application is built once. The bundler sees all the code, removes duplicates and decides how to split it. A micro-frontend platform is built in pieces by different pipelines, and no bundler sees the whole picture. The optimization that the build tool did for free now has to be done by agreement between teams.
| Cost | Cause | Main control |
|---|---|---|
| Duplicated dependencies | Remotes bundle their own copy of a library | Shared singletons with aligned versions |
| More requests | Manifest, remote entry and chunks per remote | Lazy loading, caching, preloading |
| Loading waterfall | Each step discovers the next one | Early manifest, preloading |
| Duplicated CSS | Each remote ships the design system styles | One shared theme loaded by the host |
| Extra startup work | Each remote runs its own bootstrap code | Load only what the route needs |
Duplicated dependencies
This is often the largest cost. If the host and three remotes each bundle their own copy of Angular, RxJS and the component library, the user downloads and parses the same code four times. With a framework like Angular, two instances in one page can also cause real bugs.
The control is sharing. Module Federation and Native Federation both let you declare libraries as shared, so the first copy that loads is reused by everyone. Declare the framework and any stateful library as a singleton and enable strict version checks.
Sharing has a condition: versions must be compatible. If one remote falls behind, the runtime either loads a second copy or fails, depending on the configuration. Version alignment is covered in shared dependencies and versions.
Do not share everything. A shared library is usually loaded whole, because no build knows which parts the other remotes use. It also becomes a version that all teams must coordinate. Share the framework, the design system and the few libraries that hold state. Let small utilities be bundled inside each remote, where tree shaking still removes unused code.
Requests and the loading waterfall
A typical first load goes: host HTML, host bundles, manifest, remote entry, remote chunks, then data. Each step starts only when the previous one finishes.
Ways to flatten it:
- Load remotes lazily, per route. A user who opens the orders area should not download billing.
- Request the manifest early, or inline it in the host HTML when the deployment model allows it.
- Preload the likely next remote. When the browser is idle, or when the user hovers a navigation link, fetch that remote entry in advance.
- Cache correctly. Files with a content hash in the name can be cached for a long time. The manifest and the remote entry files need a short cache or revalidation, because they point to the current version.
Budgets per remote
A platform budget that nobody owns is ignored. Give each remote its own budget and make its pipeline enforce it.
- Size budget. The Angular CLI supports
budgetsin the build configuration, with warning and error thresholds. - Shared dependency check. Fail the build if a remote bundles a library that should come from the shared scope. A bundle analyzer report in the pipeline makes this visible.
- Route-level budget. Set a target for the time to render each area's main screen on a reference device and network.
The platform team owns the budget of the host. Every user pays for the host on every visit, so it should stay small: layout, navigation, session and the loading logic. See the micro-frontend host shell.
Measuring
Lab tools show what a build would cost. Field data shows what users experience. You need both.
- Field data. Collect Core Web Vitals (LCP, INP, CLS) from real users and tag every measurement with the route and the remote that owns it. Without the tag, a slow remote hides inside a global average.
- Remote load time. Measure the time from the start of a navigation to the moment the remote is mounted. The browser's Performance API can mark both points.
- Lab checks in the pipeline. Run Lighthouse or a similar tool against the composed application on each release of the host and of each remote.
Runtime cost after loading
Download size is only one part. Several remotes on one screen also mean more JavaScript to parse and more rendering work. This is one reason to split by business area and avoid one remote per widget. More on that in common micro-frontend mistakes.
A realistic target: the first load of a micro-frontend platform should be close to what a well-built single application achieves for the same screen. If it is far worse, the usual causes are a duplicated framework, a heavy host or remotes that load eagerly.
More in this section
- Testing micro-frontends: a practical strategy
- Monorepo vs polyrepo for micro-frontends
- From frontend monolith to micro-frontends, step by step
- Running a legacy AngularJS app inside a micro-frontend host
Planning a migration or a micro-frontend platform?
In a 20-minute call you describe your situation and I tell you how I'd approach it. If it isn't worth doing yet, I'll say so.
Message me on LinkedIn