Micro‑Frontend Implementation in Meituan Waimai Advertising Platform

Meituan Waimai’s advertising team tackled duplicated business logic and slow builds across PC, H5, and KA by merging code into a monorepo and adopting a React‑based central‑router micro‑frontend architecture, where a host app dynamically loads sub‑apps with independent routing, stores, CSS, and hot‑module replacement, achieving modularization, faster builds, and isolated deployment.

Meituan Technology Team
Meituan Technology Team
Meituan Technology Team
Micro‑Frontend Implementation in Meituan Waimai Advertising Platform

Micro‑frontend is the front‑end application of the micro‑service concept. Meituan Waimai’s advertising team applied a React‑based central‑router micro‑frontend architecture to address the growing complexity of their PC, H5, and KA systems.

Background : The three systems were originally developed and maintained independently, leading to duplicated business logic, large repository size, slow builds, and cross‑team coupling. The team needed a way to reuse code physically (across repositories) and logically (shared business logic).

Physical reuse solution : Instead of using NPM packages or Git subtrees, the three systems were merged into a single monorepo with a common folder that provides shared assets. This eliminated the need for frequent pulling of shared files.

Problems after merging included rapid file growth, uncontrolled structure, slower CI/CD, and lack of isolation between business lines.

Micro‑frontend approach : The team adopted a “central‑router based micro‑frontend” using React. The architecture consists of a base (host) application that manages routing, global store, and shared libraries, and multiple child applications (sub‑apps) that each implement a single business line and contain PC, H5, and KA code.

Key design points :

Unified technology stack (React, React‑Router, Redux, Redux‑Saga).

Independent development and deployment of sub‑apps.

Dynamic routing, store, and CSS loading.

Dynamic routing is handled by React‑Router (v5). The host loads a sub‑app’s entry script and registers its routes at runtime. Example for React‑Router v3:

// react-router V3 used to receive sub‑app routes
export default () => (
  <Route
    path="/subapp"
    getChildRoutes={(location:any, cb:any) => {
      const { pathname } = location.location;
      const id = pathname.split('/')[2];
      const subappModule = (subAppMapInfo as any)[id];
      if (subappModule) {
        if (subappRoutes[id]) {
          cb(null, [subappRoutes[id]]);
          return;
        }
        currentPrefix = id;
        loadAsyncSubapp(subappModule.js)
          .then(() => { cb(null, [subappRoutes[id]]); })
          .catch(() => { console.log('loading failed'); });
      } else {
        goBackToIndex();
      }
    }}
  />
);

For React‑Router v4 (used in production):

export const AyncComponent: React.FC<{ hotReload?: number } & RouteComponentProps> = ({ location, hotReload }) => {
  const [ayncLoaded, setAyncLoaded] = useState(false);
  const [subAppMapInfoLoaded, setSubAppMapInfoLoaded] = useState(false);
  const [ayncComponent, setAyncComponent] = useState(null);
  const { pathname } = location;
  const id = pathname.split('/')[2];
  useEffect(() => {
    if (!subAppMapInfoLoaded) {
      fetchSubappUrlPath(id)
        .then(data => { subAppMapInfo = data; setSubAppMapInfoLoaded(true); })
        .catch(() => { goBackToIndex(); });
      return;
    }
    const subappModule = (subAppMapInfo as any)[id];
    if (subappModule) {
      if (subappRoutes[id]) {
        setAyncLoaded(true);
        setAyncComponent(subappRoutes[id]);
        return;
      }
      currentPrefix = id;
      setAyncLoaded(false);
      const jsUrl = subappModule.js;
      loadAsyncSubapp(jsUrl)
        .then(() => { setAyncComponent(subappRoutes[id]); setAyncLoaded(true); })
        .catch(() => { setAyncLoaded(false); console.log('loading failed...'); });
    } else {
      goBackToIndex();
    }
  }, [id, subAppMapInfoLoaded, hotReload]);
  return ayncLoaded ? ayncComponent : null;
};

Dynamic store : The host uses Redux. Sub‑apps can register their own reducers and sagas via the registerApp interface, but in practice each sub‑app maintains its own store to keep isolation.

Example of the registration interface:

import reducers from 'common/store/labor/reducer';
import sagas from 'common/store/labor/saga';
import routes from './routes/index';
function registerApp(dep: any = {}): any {
  return { routes, reducers, sagas };
}
export default registerApp;

Dynamic CSS : CSS files are loaded asynchronously before the corresponding JS entry to avoid flash‑of‑unstyled‑content. A PostCSS plugin adds a unique namespace per business line to prevent style conflicts.

Global library exposure allows sub‑apps to use shared libraries (React, React‑DOM, React‑Router‑DOM, Axios, etc.) without bundling them again:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as ReactRouterDOM from 'react-router-dom';
import * as Axios from 'axios';
// ... other imports
function registerGlobal(root: any, deps: any) {
  Object.keys(deps).forEach(key => { root[key] = deps[key]; });
}
registerGlobal(window, { React, ReactDOM, ReactRouterDOM, Axios, /* ... */ });
export default registerGlobal;

Development & deployment : Two development modes are supported – a shared host dev environment or local host + sub‑app. Hot Module Replacement (HMR) is achieved by re‑triggering a JSONP hook in the host when a sub‑app’s module is updated.

Hot‑update snippet:

// In sub‑app entry file
if ((module as any).hot) {
  (module as any).hot.accept('./routes/index', () => {
    window.wmadSubapp(registerApp, true); // notify host for hot reload
  });
}
export default registerApp;

Deployment : Sub‑apps are built independently, uploaded to CDN, and their resource URLs are stored in a central configuration (Portm). The host reads this config to load the correct assets. Talos, Meituan’s internal deployment tool, handles publishing and rollback via content‑hash versioning.

Monitoring : Load success/failure of JSON, JS, and CSS resources is reported to internal monitoring systems (CAT and TianWang). Alerts are generated on repeated failures.

Conclusion : The micro‑frontend transformation achieved:

Domain‑level (business‑line) modularization, improving maintainability.

Physical code reuse across PC, H5, and KA.

Build time reduction from minutes to seconds.

Hot‑update support without degrading developer experience.

Independent development, deployment, and rollback of sub‑apps.

Low learning curve for developers familiar with standard React SPA development.

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.

modularizationReactDeploymentmicro-frontendFrontend Architecturehot-reload
Meituan Technology Team
Written by

Meituan Technology Team

Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.

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.