Why Micro Frontends Are Changing Large-Scale Frontend Architecture

This article explains how micro frontends, inspired by micro‑service principles, break down monolithic front‑end applications into independent sub‑applications, covering design motivations, practical implementation steps, sandbox isolation techniques, and code examples to improve maintainability and performance.

ELab Team
ELab Team
ELab Team
Why Micro Frontends Are Changing Large-Scale Frontend Architecture

1. Introduction: What is a microservice?

Microservices have become a hot topic in recent years. From a front‑end perspective, we can view a system composed of PC Web, mobile H5, and an admin portal as a set of services.

Problems of a monolithic front‑end include:

Large codebase makes rapid onboarding and build difficult.

Duplicate APIs across systems.

Complex database design.

Microservices solve these by splitting the system into independent services managed via gateways and controllers.

In the front‑end world, micro‑frontend adopts the same idea: decouple logic by splitting services.

2. Front‑end Microservice Design

2.1 Why does the front‑end need microservices?

Day 1: build 20 s
Week 1: build 1 min
Month 1: build 5 min

As the project grows, a single large application becomes hard to maintain, leading to low development efficiency and difficult deployments.

2.2 Micro‑frontend application scenarios

A typical management system has many tabs and nested routes, which become hard to maintain over time.

With micro‑frontend, each tab becomes an independent sub‑application with its own state, scope, and bundle, managed by a single master application.

In short: Application routing → route dispatches application.

2.3 Early micro‑frontend approach – iFrame

Why not iframe?

Using an iframe can dynamically load sub‑applications by changing its src attribute.

Advantages:

Self‑contained styles.

Sandbox isolation.

Independent execution.

Drawbacks:

CSS synchronization issues.

Complex communication (postMessage).

Cannot share components.

Potential performance and memory overhead.

Modern micro‑frontend designs aim to keep iframe benefits while solving these problems.

3. Core Logic of Micro‑frontend

3.1 Sub‑application Loading (Loader)

Typical flow:

First load the master, then let the master decide which registered sub‑application to load, finally render the sub‑app via vue‑router or react‑router.

3.1.1 Registration

// Assume our micro‑frontend framework is called hailuo
import Hailuo from './lib/index';

// 1. Declare sub‑applications
const routers = [
  {
    path: 'http://localhost:8081',
    activeWhen: '/subapp1'
  },
  {
    path: 'http://localhost:8082',
    activeWhen: '/subapp2'
  }
];

// 2. Register sub‑applications
Hailuo.registerApps(routers);

// 3. Run micro‑frontend
Hailuo.run();

Registration simply stores an array of sub‑apps.

registerApps(routers: Router[]) {
  (routers || []).forEach(r => {
    this.Apps.push({
      entry: r.path,
      activeRule: location => location.href.indexOf(r.activeWhen) !== -1
    });
  });
}

3.1.2 Intercept

We intercept routing events to control the timing of main/sub‑app logic.

import Hailuo from "./";

const EVENTS_NAME = ['hashchange', 'popstate'];
const EVENTS_STACKS = { hashchange: [], popstate: [] };

const handleUrlRoute = (...args) => {
  // Load the corresponding sub‑app
  Hailuo.loadApp();
  // Execute sub‑app route handlers
  callAllEventListeners(...args);
};

export const patch = () => {
  window.addEventListener('hashchange', handleUrlRoute);
  window.addEventListener('popstate', handleUrlRoute);

  const originalAddEventListener = window.addEventListener;
  const originalRemoveEventListener = window.removeEventListener;

  window.addEventListener = (name, handler) => {
    if (name && EVENTS_NAME.includes(name) && typeof handler === 'function') {
      if (!EVENTS_STACKS[name].includes(handler)) {
        EVENTS_STACKS[name].push(handler);
      }
      return;
    }
    return originalAddEventListener.call(this, name, handler);
  };

  window.removeEventListener = (name, handler) => {
    if (name && EVENTS_NAME.includes(name) && typeof handler === 'function') {
      const idx = EVENTS_STACKS[name].indexOf(handler);
      if (idx !== -1) {
        EVENTS_STACKS[name].splice(idx, 1);
      }
      return;
    }
    return originalRemoveEventListener.call(this, name, handler);
  };

  // Patch pushState / replaceState to emit route changes
  const createPopStateEvent = (state, name) => {
    const evt = new PopStateEvent('popstate', { state });
    evt['trigger'] = name;
    return evt;
  };

  const patchUpdateState = (updateState, name) => () => {
    const before = window.location.href;
    updateState.apply(this, arguments);
    const after = window.location.href;
    if (before !== after) {
      handleUrlRoute(createPopStateEvent(window.history.state, name));
    }
  };

  window.history.pushState = patchUpdateState(window.history.pushState, 'pushState');
  window.history.replaceState = patchUpdateState(window.history.replaceState, 'replaceState');
};

3.1.3 Loading

After matching a sub‑app via routing, we load its HTML and scripts.

async loadApp() {
  // Find the active sub‑app
  const shouldMountApp = this.Apps.filter(this.isActive);
  const app = shouldMountApp[0];
  const subapp = document.getElementById('submodule');

  await fetchUrl(app.entry)
    .then(text => { subapp.innerHTML = text; });

  const res = await fetchScripts(subapp, app.entry);
  if (res.length) {
    execScript(res.reduce((t, c) => t + c, ''));
  }
}

Better practice – html‑entry library loads and processes HTML, JS, CSS of a micro‑app in four steps:

1. Request sub‑app entry HTML.

2. Strip html / head tags and handle static resources.

3. Process source maps, sandbox JS, locate entry script.

4. Retrieve sub‑app provider content.

export function provider({ dom, basename, globalData }) {
  return {
    render() {
      ReactDOM.render(
        <App basename={basename} globalData={globalData} />,
        dom ? dom.querySelector('#root') : document.querySelector('#root')
      );
    },
    destroy({ dom }) {
      if (dom) {
        ReactDOM.unmountComponentAtNode(dom);
      }
    }
  };
}

3.2 Sandbox

A sandbox isolates each sub‑application’s side effects (global variables, styles) to avoid conflicts.

Why we need sandbox?

Key is to control when the sandbox is activated and deactivated.

3.2.1 Snapshot Sandbox

Take a snapshot of the environment before switching, and restore it when returning.

// Switch to environment A
window.a = 2;
// Switch to environment B
window.a = 3;
// Switch back to A
console.log(a); // 2
class Sandbox {
  private original;
  private mutated;
  sandBoxActive = () => {};
  sandBoxDeactivate = () => {};
}

const sandbox = new Sandbox();
const code = "...";
sandbox.activate();
execScript(code);
sandbox.sandBoxDeactivate();

3.2.2 VM Sandbox

Similar to Node’s vm module, it creates an isolated execution context using Proxy.

class SandBox {
  execScript(code) {
    const varBox = {};
    const fakeWindow = new Proxy(window, {
      get(target, key) { return varBox[key] || window[key]; },
      set(target, key, value) { varBox[key] = value; return true; }
    });
    const fn = new Function('window', code);
    fn(fakeWindow);
  }
}

export default SandBox;

const sandbox = new Sandbox();
sandbox.execScript(code);
const sandbox2 = new Sandbox();
sandbox2.execScript(code2);

3.2.3 CSS Sandbox

Webpack adds style tags via appendChild; we intercept this to namespace CSS.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Module FederationFrontend ArchitecturesandboxCode Splitting
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.