Frontend Development 35 min read

Integrating Monorepo with Micro‑Frontend Using pnpm and Micro‑App: Architecture, Implementation, and Deployment

This article explains how to combine Monorepo and micro‑frontend architectures using pnpm and Micro‑App, covering the concepts, technical selections, step‑by‑step setup of base and child applications, shared modules, configuration, code standards, mock services, Docker/Nginx deployment, and best practices for frontend development.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Integrating Monorepo with Micro‑Frontend Using pnpm and Micro‑App: Architecture, Implementation, and Deployment

Introduction

This article introduces how to combine Monorepo and micro‑frontend technologies. It acknowledges the debates around both approaches and focuses on a practical implementation using Micro‑App and pnpm, guiding readers from zero‑setup to full project deployment.

Understanding Monorepo and Micro‑Frontend

Micro‑frontend can significantly reduce the cost of integrating new technologies into existing projects, while Monorepo provides a centralized codebase that facilitates sharing components, libraries, and tools across multiple micro‑frontend applications.

The article demonstrates the architecture using pnpm monorepo + Micro‑App as an example.

Problems Solved

Complex code management across multiple repositories.

Difficulty in deploying multiple independent projects.

Duplicate modules and high maintenance cost.

Low development efficiency due to frequent repository switching.

Typical Application Scenarios

Micro‑frontend is especially suitable for backend management systems, e‑commerce platforms, financial systems, and any project with multiple business subsystems that need data sharing and integration.

Technical Selection

Monorepo Options

Type

Solution

Description

Build‑oriented

Turborepo, Rush, Nx

Optimizes build performance for large codebases.

Lightweight

Lerna, Yarn, pnpm

Gradual adoption, focuses on dependency and version management.

The article recommends the lightweight pnpm approach, optionally combined with changesets for version control.

Micro‑Frontend Frameworks

Single‑spa (not recommended: no communication, no sandbox, style conflicts).

Qiankun – the mainstream solution based on Single‑spa.

Micro‑App – native Web Component based, provides JS sandbox, style isolation, pre‑loading, and a plugin system.

Wujie – similar to Micro‑App but uses iframe sandbox.

EMP – built on Webpack 5 Module Federation, offers fine‑grained module sharing.

The author chooses Micro‑App for its low entry cost and comprehensive features.

Technical Practice

Setting Up a pnpm Monorepo

Project structure:

├── packages
│   ├── pkg1
│   │   └── package.json
│   ├── pkg2
│   │   └── package.json
│   └── package.json
├── pnpm-workspace.yaml

Install pnpm globally:

npm install -g pnpm

Initialize the monorepo:

pnpm init

Create pnpm-workspace.yaml :

packages:
  - 'packages/*'

Installing Micro‑App

Install Micro‑App at the workspace root:

pnpm add micro-app -w

The -w flag installs the dependency to the workspace root.

Building the Base Application

Use Vite (fast ES‑Module based builder) to create a Vue 3 base app:

pnpm add vite -D
pnpm create vite --template vue packages/base

Building Child Applications

Child apps are built with Vue‑CLI (Vue 2) because Micro‑App currently has limited Vite support:

vue create child-app
# or use @vue/cli directly

Other frameworks (React, Angular, Svelte) can also be used.

Routing Configuration

Example of a Vue‑Router history mode that respects the base route injected by Micro‑App:

import { createRouter, createWebHistory } from 'vue-router'
import routes from './router'

const router = createRouter({
  history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL),
  routes,
})

Public‑path handling for Micro‑App:

if (window.__MICRO_APP_ENVIRONMENT__) {
  __webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}

Cross‑Origin Settings

Development: configure devServer.headers to allow all origins.

devServer: {
  headers: {
    'Access-Control-Allow-Origin': '*',
  },
},

Production: configure Nginx (see later).

Unified Application Configuration

Place a config.json at the repository root to describe each app (name, packageName, port, etc.). Example snippet:

{
  "base": {
    "name": "Base",
    "packageName": "@package/core",
    "port": 4000
  }
}

Base Application Login Guard

Use Vue‑Router navigation guards to enforce authentication based on the auth flag in config.json :

router.beforeEach(async (to, from, next) => {
  const token = storage.get('authKey')
  NProgress.start()
  if (to.matched.some(r => r.meta.auth)) {
    if (token && token !== 'undefined') {
      if (to.path === '/login') next({ path: '/home' })
      else next()
    } else {
      next({ name: 'Login', query: { redirect: to.fullPath } })
      NProgress.done()
    }
  } else {
    next()
  }
})

Application Navigation and Communication

Base app decides whether to navigate directly or ask the child app to handle the route:

import microApp, { getActiveApps } from '@micro-zoe/micro-app'

if (!getActiveApps().includes(appName)) {
  router.push(`/${appName}${path}`)
} else {
  microApp.setData(appName, { path })
}

Child apps can send data back to the base via window.microApp.dispatch and listen with addDataListener .

Shared Common Modules

Example of a shared request wrapper with TypeScript typings:

export interface Response
{
  errors: Errors
  response: ResponseResult
response_code: number
  success: boolean
}

export interface ListResult
{
  current: number
  records: T[]
  size: number
  total: number
}

Consume the shared request in an app:

import { request, Response, ListResult } from '@package/share'

export function getChargingAnalysis() {
  return request
>>({
    url: '...',
    method: 'post',
  })
}

Packaging the Shared Module

Rollup configuration (simplified):

import typescript from 'rollup-plugin-typescript2'
import autoprefixer from 'autoprefixer'
import pkg from './package.json'

export default {
  input: 'src/index.ts',
  output: [{ file: pkg.main, format: 'esm', sourcemap: false }],
  plugins: [
    typescript({
      tsconfigOverride: { compilerOptions: { declaration: true, module: 'ESNext' } },
      check: false,
    }),
  ],
}

Atomic CSS

Adopt Tailwind, Uno, or similar atomic CSS frameworks to avoid style conflicts across micro‑frontends. The article suggests disabling Micro‑App’s built‑in style isolation and relying on atomic CSS for consistency.

Global Environment Variables

Vite: use envDir and envPrefix to load variables from the repository root.

export default defineConfig({
  envDir: './../../',
  envPrefix: 'VUE_APP',
})

Webpack: use dotenv to load a custom .env file.

const dotenv = require('dotenv')
const path = require('path')
module.exports = function setGlobalEnv() {
  let envfile = '.env'
  if (process.env.NODE_ENV) envfile += `.${process.env.NODE_ENV}`
  dotenv.config({ path: path.resolve('../../', envfile) })
}

Unified Code Standards

Configure ESLint at the root with overrides for Vue 2 and Vue 3 + TS projects, and add a .eslintignore to exclude all node_modules directories.

Mocking APIs

Use msw + faker to create mock handlers. Example mock for a paginated task history endpoint:

import { rest } from 'msw'
import { faker } from '@faker-js/faker/locale/zh_CN'

const baseUrl = process.env.VUE_APP_API_BASE_URL

export default [
  rest.post(`${baseUrl}/business/history/page`, (req, res, ctx) =>
    res(
      ctx.delay(),
      ctx.status(200),
      ctx.json({
        errors: null,
        response: {
          code: 200,
          message: '检索成功。',
          result: {
            current: req.body.pageNo,
            size: req.body.pageSize,
            total: faker.datatype.number({ min: 100, max: 500 }),
            records: new Array(req.body.pageSize).fill(1).map(() => ({
              agencyCode: faker.random.word(),
              createTime: faker.date.past(),
              id: faker.datatype.uuid(),
              vin: faker.vehicle.vin(),
            }))),
          },
        },
        response_code: 2000,
        success: true,
      })
    )
  ),
]

Start the mock worker in development when the mock flag is enabled in config.json :

if (process.env.NODE_ENV === 'development' && isMock) {
  const { worker } = require('../../../mocks/browser')
  worker.start()
}

Development & Build Scripts

Custom dev.js script uses inquirer to select which child app to run, automatically starts the base app if it is not already running, and checks port occupancy with detect-port .

Custom build.js script lets the user choose multiple packages to build, then runs each package’s npm run build sequentially.

Docker & Nginx Deployment

Dockerfile based on the latest Nginx image copies the built dist folder and a custom nginx.conf into the container, exposing port 80 and running Nginx in the foreground.

FROM nginx:latest
COPY dist /usr/share/nginx/html
COPY nginx.conf /usr/share/nginx/conf/nginx.conf
EXPOSE 80
CMD ["nginx", "-c", "/usr/share/nginx/conf/nginx.conf", "-g", "daemon off;"]

Docker Compose defines a single web service that builds the image from the Dockerfile and maps host port 80 to container port 80.

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: web
    container_name: web
    ports:
      - "80:80"

Nginx Configuration for Micro‑Frontend

The provided nginx.conf routes /base and /child paths, enables gzip compression, sets long‑term cache headers for static assets, and adds CORS headers.

server {
    listen 80;
    server_name localhost;
    gzip on;
    location /base {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /base/index.html;
    }
    location ^~ /child {
        root /usr/share/nginx/html;
        try_files $uri $uri/ index.html;
    }
}

Conclusion

The article demonstrates a complete workflow for building, sharing, testing, and deploying a micro‑frontend system powered by a Monorepo, covering architecture decisions, code organization, tooling, mock services, CI scripts, and production deployment with Docker and Nginx.

frontendDockerMonorepomicro-frontendvitepnpmmicro-app
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

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.