Optimization Techniques
Optimizing micro frontends requires a different lens than optimizing a monolithic application. With code split across multiple independently deployed bundles, performance concerns multiply: duplicate dependencies, unpredictable load order, and cross-boundary overhead. This guide covers proven techniques to analyze, optimize, and measure performance in a micro frontend context.
Bundle Analysis and Optimization
Section titled “Bundle Analysis and Optimization”Before optimizing, you must understand what you’re shipping. Bundle analysis for micro frontends goes beyond a single build output—you need visibility across every micro frontend and the host application.
What to Analyze
Section titled “What to Analyze”- Per-micro-frontend bundle size: Each MF should have a known, auditable footprint.
- Shared dependency footprint: Frameworks, utilities, and design system components often appear multiple times if not properly shared. See Shared Dependencies for deduplication strategies.
- Chunk composition: Identify large third-party libraries, lazy-loaded routes, and dynamic imports.
- Duplicate code: Use tools like
source-map-explorer,webpack-bundle-analyzer, orrollup-plugin-visualizerto spot redundancy across boundaries.
Recommended Tools
Section titled “Recommended Tools”- Webpack:
webpack-bundle-analyzergenerates treemaps of your output. - Vite: Built-in
--mode analyzeorrollup-plugin-visualizerfor Rollup-based builds. - Rspack: Compatible with Webpack analysis plugins.
Run analysis on each micro frontend separately and on the composed application to catch both local bloat and cross-MF duplication.
Code Splitting Strategies
Section titled “Code Splitting Strategies”Code splitting reduces initial load by deferring non-critical code. In micro frontends, splitting happens at multiple layers.
Per-Route Splitting
Section titled “Per-Route Splitting”Each route loads only the code it needs. A product detail route loads the product MF; a checkout route loads the checkout MF. This is the default when micro frontends are composed by route.
Implementation: Use framework routing (React Router, Vue Router, etc.) with lazy-loaded route components. Each route becomes a dynamic import() that loads the corresponding MF or chunk.
// Example: route-based lazy loadconst ProductPage = () => import('./micro-frontends/ProductApp');const CheckoutPage = () => import('./micro-frontends/CheckoutApp');Per-Micro-Frontend Splitting
Section titled “Per-Micro-Frontend Splitting”Each micro frontend is a separate bundle. The host loads MFs on demand rather than bundling them into a single build.
Benefits: Teams deploy independently; users download only the MFs they navigate to. The trade-off is additional round trips when switching between MFs.
Dynamic Imports
Section titled “Dynamic Imports”Use import() for on-demand loading of heavy features (modals, dashboards, admin panels). Combine with preloading for critical paths—see Loading Strategies for prefetch and preload patterns.
Best practice: Keep the initial shell and above-the-fold content minimal. Defer below-the-fold and secondary features with dynamic imports.
Tree Shaking Across Micro Frontend Boundaries
Section titled “Tree Shaking Across Micro Frontend Boundaries”Tree shaking removes unused code from bundles. In micro frontends, each MF is built separately, so tree shaking applies per-MF. The challenge: shared dependencies.
If each MF bundles its own copy of a utility library, you lose tree shaking benefits at the boundary. The solution is to externalize or share common dependencies so they appear once. Module Federation, Import Maps, and shared chunks achieve this—see Shared Dependencies for implementation details.
When tree shaking works correctly:
- Each MF only includes the code it actually uses.
- Shared dependencies (React, design tokens) are loaded once and reused.
- Barrel files (
index.tsre-exporting many modules) can undermine tree shaking—prefer direct imports or well-structured public APIs.
Caching Strategies
Section titled “Caching Strategies”Effective caching reduces repeat visits and CDN load. Micro frontends introduce complexity: multiple origins, versioned assets, and coordinated deployments.
Long-Term Caching with Content Hashing
Section titled “Long-Term Caching with Content Hashing”Use content hashes in filenames (main.abc123.js) so unchanged assets stay cached across deployments. When you redeploy an MF, only its changed chunks get new hashes; the rest remain cached.
Configuration: Most bundlers enable this by default. Ensure output.filename (Webpack) or equivalent includes [contenthash].
CDN Caching Layers
Section titled “CDN Caching Layers”Serve MF bundles from a CDN with appropriate cache headers:
- Immutable assets (hashed filenames):
Cache-Control: max-age=31536000, immutable - HTML shell: Short cache or no cache if it references MF entry points that change
- API responses: Vary by use case; avoid caching user-specific data
Cache Invalidation on Deployment
Section titled “Cache Invalidation on Deployment”When you deploy a new MF version:
- New hashed files: Automatically bypass old cache; no invalidation needed.
- Entry point manifest: If the shell fetches a manifest (e.g.,
mf-manifest.json) listing MF URLs, that manifest should have a short cache or be invalidated on deploy. - CDN purge: For files without hashes, use your CDN’s purge API to invalidate on deploy.
Shared Dependency Caching
Section titled “Shared Dependency Caching”When MFs share a common dependency (e.g., React), ensure it’s served from a single URL so the browser caches it once. Split chunks or Module Federation shared modules achieve this—see Shared Dependencies for details.
Minimizing Duplicate Code Across Micro Frontends
Section titled “Minimizing Duplicate Code Across Micro Frontends”Duplicate code inflates bundle size and complicates updates. Common sources:
- Frameworks: React, Vue, Angular—each MF should use a shared instance when possible.
- Utilities: Lodash, date libs, validation—externalize to a shared package or runtime module.
- Design system: Buttons, inputs, tokens—ship a shared design system package consumed by all MFs.
Strategies:
- Shared package: Publish a private npm package; all MFs depend on it. Use semantic versioning and avoid breaking changes.
- Module Federation / Import Maps: Share at runtime so the host provides the dependency once.
- Monorepo: Co-locate shared code; enforce boundaries with lint rules and build config.
Performance Budgets: Setting and Enforcing Limits
Section titled “Performance Budgets: Setting and Enforcing Limits”A performance budget defines acceptable limits for bundle size, load time, or Core Web Vitals. In micro frontends, budgets apply per-MF and for the composed app.
What to Budget
Section titled “What to Budget”- Initial JS (per MF): e.g., “No MF exceeds 150 KB gzipped.”
- Total JS on initial load: e.g., “Shell + first MF under 300 KB gzipped.”
- Largest Contentful Paint (LCP): e.g., “LCP under 2.5s on 4G.”
How to Enforce
Section titled “How to Enforce”- CI: Run bundle analysis in CI; fail the build if budgets are exceeded.
- Bundler plugins:
bundlesize,size-limit, or custom scripts in your pipeline. - Lighthouse CI: Integrate Lighthouse into CI for real-user metrics and lab data.
Budgets encourage teams to stay within limits and surface regressions before production.
Measuring Performance: Core Web Vitals in Micro Frontend Contexts
Section titled “Measuring Performance: Core Web Vitals in Micro Frontend Contexts”Core Web Vitals (LCP, FID/INP, CLS) measure real-user experience. In micro frontends:
- LCP: Often determined by the shell and the first MF. Optimize above-the-fold content and defer below-the-fold MFs.
- INP (Interaction to Next Paint): Large JS parse/compile time from multiple MFs can delay interactivity. Reduce total JS and prioritize critical path.
- CLS: Layout shifts can occur when MFs load asynchronously and inject content. Use skeleton screens, reserved space, or server-rendered shells to stabilize layout—see Loading Strategies for patterns.
Measurement Options
Section titled “Measurement Options”- Real User Monitoring (RUM): Web Vitals API, Google Analytics 4, or dedicated RUM tools.
- Lab testing: Lighthouse, WebPageTest—useful for CI and pre-release checks.
- Custom instrumentation: Measure time-to-interactive per MF, or track which MFs contribute to LCP.
Cross-reference with Loading Strategies for loading optimizations that directly impact Core Web Vitals.
Summary
Section titled “Summary”Optimizing micro frontends requires bundle analysis across all MFs, strategic code splitting (per-route, per-MF, dynamic imports), tree shaking with shared dependencies, and robust caching (content hashes, CDN, invalidation). Performance budgets and Core Web Vitals measurement keep teams accountable. The distributed nature of micro frontends amplifies the importance of shared dependency management—invest in that foundation first.
Go Deeper in the Book
This topic is covered in depth in Chapter 9 of Micro Frontends Architecture for Scalable Applications, with detailed examples, diagrams, and production-ready patterns.