Frontend Development 15 min read

Micro‑Frontend Architecture: Benefits, Implementation Options, and Technical Details

This article explains why rapid business growth and rising development costs push teams toward a micro‑frontend architecture, compares monolithic and micro‑frontend approaches, outlines four implementation patterns, presents the chosen Vue‑based tech stack, and shares code snippets for communication, scrolling, and build‑time optimizations.

Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Micro‑Frontend Architecture: Benefits, Implementation Options, and Technical Details

The business has expanded quickly, adding many access channels (Baidu, quick‑apps, etc.) and more developers, which increased both development and management costs while decreasing efficiency; repeated development of the same modules led the team to consider a micro‑frontend solution.

Traditional monolithic front‑ends suffer from long build times and slow market response because of heavy inter‑module dependencies; each new site copy multiplies development and testing effort.

Micro‑frontends bring three main advantages: higher safety and lower testing cost through service isolation, flexible expansion by allowing each service to use its own technology stack, and independent agility where each module can be developed, tested, and deployed separately.

Four practical implementation schemes are discussed:

Scheme A – Backend template engine insertion : adds backend complexity and returns rendering control to the server; rarely chosen by front‑end developers but useful for pure display pages.

Scheme B – Client‑side JavaScript async loading : uses module loaders such as AMD or CMD; impacts development efficiency but keeps control on the front end.

Scheme C – WebComponents integration : leverages modern standards to bundle all modules as custom elements; may suffer compatibility issues on older browsers and requires a full rewrite of the app.

Scheme D – iframe isolation : provides strong runtime isolation, allows each module to use any framework, and enables simple message passing via postMessage ; drawbacks include larger bundle size, performance concerns with deep iframe nesting, and difficulty handling mobile iframes.

The chosen technical stack for the micro‑frontend platform includes:

Main framework: vue + vue‑router + vuex

Communication: URL parameters + postMessage + vue‑unicom (internal broadcast)

Scrolling: iscroll

Layout: px2rem (750‑based design)

Hosting: iframe + webview

Static asset caching: asset‑cache‑webpack‑plugin for offline support and pre‑loading

Below is a core part of the data‑communication bridge used by the micro‑frontend modules:

// router
import router from '../../router'
import getSole from 'rimjs/sole'
import { unicomEmit } from 'rimjs/vueUnicom'
import { env } from '../env'
let win = window
function postMessage(data, source = window.parent) {
  source.postMessage(data, '*')
}
let postMessageResolveFns = {}
let messageInData = null
let messageOutData = {}
let query = router.query
if (query._in != null) {
  messageInData = {}
}
if (messageInData) {
  try {
    Object.assign(messageInData, JSON.parse(query._in) || {})
  } catch (e) {}
}
function setUser(inEnv) {
  if (!inEnv) return
  env.userId = inEnv.userId || ''
}
async function getUser() {
  let inEnv = await bridge.postMessage('env:get')
  setUser(inEnv)
}
function onMessage({ type, insruct, data } = {}, source) {
  let backData = null
  if (type == 'unicom') {
    backData = data == null ? {} : data
    unicomEmit(insruct, backData)
    return
  }
  if (type == 'system') {
    if (insruct == 'font') {
      document.documentElement.style.fontSize = data
      return
    }
    if (insruct == 'href') {
      setUser(data.env)
      if (data.replace) {
        window.location.replace(window.location.pathname + '#' + data.href)
      } else {
        window.location.href = window.location.pathname + '#' + data.href
      }
      return
    }
    if (insruct == 're_init') {
      window.location.replace(window.location.pathname + '#/init?re=1')
      return
    }
  }
  if (type == 'back') {
    let resolveFn = postMessageResolveFns[insruct]
    if (resolveFn) {
      resolveFn(data)
      delete postMessageResolveFns[insruct]
    }
    return
  }
  if (fnKey) {
    postMessage({ type: 'back', from: 'microservice', insruct: fnKey, data: backData }, source)
  }
}
window.addEventListener('message', function (ev) {
  let opt = ev.data || {}
  let { from } = opt
  if (from != 'microservice') return
  onMessage(opt, ev.source)
})
export let bridge = {
  postMessage(opt, data) {
    if (typeof opt == 'string') {
      let x = opt.match(/^([^:]*):*(.*)$/)
      if (!x) x = [opt]
      if (!x[2]) { x[2] = x[1]; x[1] = 'unicom' }
      opt = { data, type: x[1], insruct: x[2] }
    }
    return new Promise(function (resolve) {
      let fnKey = 'ms:' + getSole()
      postMessageResolveFns[fnKey] = resolve
      if (messageInData) {
        let key = opt.type + ':' + opt.insruct
        messageOutData[key] = data
        setTimeout(function () {
          onMessage({ type: 'back', from: 'microservice', insruct: fnKey, data: messageInData[key] || null })
        }, 0)
        return
      }
      postMessage(Object.assign({ from: 'microservice' }, opt))
    })
  },
  endBack(opt, data) {
    if (opt) {
      this.postMessage(opt, data)
    }
    if (messageInData) {
      win.wx.miniProgram.navigateBack()
      win.wx.miniProgram.postMessage({ data: messageOutData })
      return
    }
    window.history.back()
  }
}
getUser()

Scrolling inside an iframe often fails; the solution is to use iscrollbar5.2 with a custom layout component. The component’s less style and Vue‑single‑file component code are shown below:

The build configuration adds px2rem for responsive layout and the asset‑cache‑webpack‑plugin for offline caching:

const AssetCachePlugin = require('asset-cache-webpack-plugin')
module.exports = {
  devServer: { port: 9001 },
  css: { extract: false, loaderOptions: { postcss: { plugins: [require('postcss-plugin-px2rem')({
    rootValue: 100,
    exclude: /(node_module)/,
    mediaQuery: false,
    minPixelValue: 0
  })] } } },
  configureWebpack(conf) { conf.devtool = 'source-map' },
  chainWebpack(conf) {
    conf.plugin('asset-cache').use(AssetCachePlugin, [{
      exclude: [/\.map$/, /\/_/],
      comment: 'frontend micro‑service'
    }])
  }
}

Optimization measures to reduce white‑screen time include skeleton screens, offline caching via the asset‑cache plugin, loading a single micro‑frontend per iframe, and pre‑loading micro‑services for instant display.

Pros of the micro‑frontend approach are increased agility, faster testing cycles, better support for CI/CD, and simpler maintenance per team. Cons are added integration complexity, possible dependency duplication, and potential initial loading delays that may affect user experience.

Typical use cases are extracting repeatedly used modules from a monolithic app into independent micro‑frontends, especially when those modules are stable and evolve slowly, while keeping the core business flow in the original monolith.

performancemicro-frontendVueWebpackFrontend Architectureiframeiscroll
Tongcheng Travel Technology Center
Written by

Tongcheng Travel Technology Center

Pursue excellence, start again with Tongcheng! More technical insights to help you along your journey and make development enjoyable.

0 followers
Reader feedback

How this landed with the community

login 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.