DEV Community

 Aiden Shaw
Aiden Shaw

Posted on

Modern Web Architecture: From Monoliths to Micro-Frontends

How we reduced deploy times by 70% and scaled teams independently

Introduction

Last year, our React monolith hit a critical pain point: a 15-minute build pipeline, dependency hell between teams, and a 3-second TTI (Time to Interactive) regression after a "minor" Redux update. The culprit? Tightly coupled components, shared state chaos, and a single deployable artifact.

This forced us to rethink our architecture. Today, our web app runs as 6 independent micro-frontends (MFEs), deploys in under 90 seconds, and supports A/B tests at the component level. Here’s how we got here—and the hard lessons learned.


What Is Modern Web Architecture?

A shift from monolithic SPAs to distributed compositions:

  • Monoliths: Single codebase, shared dependencies (e.g., Create React App).
  • Micro-Frontends (MFEs): Decoupled apps owned by separate teams, composed at runtime.
  • Hybrid SSR/Edge: Next.js for SEO-critical pages, React SPAs for dashboards.

Technical Definition:

MFEs are autonomous fragments of a UI, loaded dynamically via:

  • Build-Time Integration (e.g., NPM packages) → High coupling.
  • Runtime Integration (e.g., Module Federation, <script> tags) → True independence.

Linux/CLI Analog:

# Monolith (single bundle)
webpack --entry ./src/index.js --output bundle.js

# Micro-Frontend (federated)
webpack --exposes "Button=./src/Button.js" --name "designSystem"
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

  1. Team Autonomy

    • Problem: Marketing team needs to update a promo banner without QA’ing the entire app.
    • Solution: Isolate the banner as an MFE. Deploys via CDN in 30 seconds.
  2. Legacy Migration

    • Problem: jQuery widget in a React app.
    • Solution: Wrap it in a Web Component, load it as an MFE.
  3. Performance Isolation

    • Problem: A bloated analytics package slows down the checkout page.
    • Solution: Lazy-load it as a separate MFE after hydration.

Topology & Tech Stack

Key Tools:

  • Module Federation (Webpack): Dynamic dependency sharing.
  • Single-SPA: Meta-framework for MFE orchestration.
  • TurboRepo: Monorepo build optimizations.

Configuration & CLI Examples

1. Module Federation Setup (webpack.config.js):

new ModuleFederationPlugin({
  name: "host",
  remotes: {
    productApp: "product@https://cdn.example.com/product/remoteEntry.js",
  },
  shared: ["react", "react-dom"], // Avoid duplicate libs
});
Enter fullscreen mode Exit fullscreen mode

2. Dynamic Loading (React):

const ProductPage = React.lazy(() => import("productApp/ProductPage"));
Enter fullscreen mode Exit fullscreen mode

3. Observability (CLI):

# Audit bundle duplicates
npx webpack-bundle-analyzer stats.json

# Measure load times
lighthouse http://localhost:3000 --view
Enter fullscreen mode Exit fullscreen mode

Failure Scenarios & Recovery

1. Version Conflicts

  • Symptom: App crashes due to multiple React versions.
  • Debug: window.__webpack_require__.getVersion("react")
  • Fix: Enforce shared in Module Federation.

2. Network Bottlenecks

  • Symptom: MFEs timeout in low-connectivity regions.
  • Debug: navigator.connection.effectiveType + CrUX data.
  • Fix: Preload critical MFEs; fallback to SSR.

3. CSS Collisions

  • Symptom: Global styles from one MFE leak into another.
  • Fix: Use CSS-in-JS (e.g., Emotion) or Shadow DOM.

Performance & Optimization

  • Bundle Splitting: Each MFE loads <100kb (critical path).
  • Prefetching:
  <link rel="prefetch" href="https://cdn.example.com/cart.js" as="script" />
Enter fullscreen mode Exit fullscreen mode
  • Edge Caching: Serve MFEs via CDN with Cache-Control: immutable.

Benchmark:


Security Implications

  • Risk: Malicious MFE injects code via eval().
    • Mitigation: Use CSP: script-src 'self' https://trusted.cdn.com.
  • Risk: Data leakage between MFEs.
    • Mitigation: Isolate state with postMessage + BroadcastChannel.

Enterprise Patterns

  1. Monorepo vs Polyrepo:
    • Tradeoff: Centralized tooling vs. team autonomy.
  2. Feature Flags:
   if (flags.useNewCheckout) {
     import("checkoutV2/App");
   }
Enter fullscreen mode Exit fullscreen mode
  1. Fallback Strategies:
    • Load a static SSR fallback if an MFE fails.

Conclusion

Micro-frontends aren’t a silver bullet—they add complexity in routing, state sharing, and tooling. But for teams scaling beyond 10+ devs, they’re a game-changer.

Next Steps:

  1. Audit your monolith’s coupling with madge --image graph.svg ./src.
  2. Prototype a non-critical MFE (e.g., footer).
  3. Measure TTI impact with web-vitals.

Toolchain: Webpack 5, Module Federation, Single-SPA, TurboRepo, Vercel Edge.

Top comments (0)