Micro-FrontendsModule FederationSingle-SPAWeb Components前端架構遷移指南2026

Micro-Frontends 實戰:從單體前端到微前端的遷移指南

整理 Module Federation、Single-SPA、Web Components 三種微前端方案的核心理念、優缺點與實際遷移步驟。幫助前端團隊評估何時該拆、怎麼拆、以及拆完後的管理策略。

· 8 分鐘閱讀

當一個前端 codebase 超過 50 萬行、超過 20 人同時在上面開發,任何改動都像在高樓外牆走鋼索。Micro-Frontends(微前端)把後端微服務的成功經驗帶到前端,讓大型前端得以用「自治團隊 + 獨立部署」的方式運作,代價是增加了相當的複雜度。這篇整理三大主流實作方案的比較與遷移策略。


什麼時候需要微前端?

微前端不是銀彈。在你開始考慮它之前,先問自己三個問題:

  1. 你的前端 codebase 超過 30 萬行嗎?
  2. 你有超過 5 個團隊在同一個前端上開發嗎?
  3. 你的部署頻率受到前端 monolithic 的影響嗎?

如果三個問題都沒有肯定的答案,你很可能不需要微前端。以下才是微前端真正有價值的訊號:

  • 不同團隊想要使用不同的技術版本:A 團隊想用 React 18,B 團隊想用 Vue 4,monolithic 的 package.json 無法調和
  • 某個子系統需要獨立部署:行銷頁面改版不需要等整個應用上線
  • 某個子系統需要單獨回滾:結帳模組出問題,不需要整站回滾
  • 程式碼所有權模糊:沒有人敢動某個老舊模組,變成技術債黑洞

三大實作方案

方案一:Module Federation(Webpack 5+)

定位:讓多個獨立 build 輸出共享同一個 runtime,解決了過去 Webpack 時代微前端最大的痛點——重複的 vendor bundle。

Module Federation 是 Webpack 5 引入的功能,核心理念是:每個團隊 build 自己的 bundle,但共享一份 runtime

// Host 應用(主應用)
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        // 遠程模組:格式為 "名稱@URL到資產清單"
        dashboard: 'dashboard@http://localhost:3001/remoteEntry.js',
        checkout: 'checkout@http://localhost:3002/remoteEntry.js',
      },
      shared: ['react', 'react-dom'], // 共享的依賴,避免重複
    }),
  ],
};
// Remote 應用(dashboard 團隊的子應用)
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'dashboard',
      filename: 'remoteEntry.js', // 暴露給 host 使用的清單
      exposes: {
        './Dashboard': './src/Dashboard',
        './Analytics': './src/Analytics',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};
// Host 應用中使用 Remote 元件
import React, { Suspense } from 'react';

const Dashboard = React.lazy(() => import('dashboard/Dashboard'));
const Analytics = React.lazy(() => import('dashboard/Analytics'));

function App() {
  return (
    <div>
      <h1>主應用 Header</h1>
      <Suspense fallback={<div>載入中...</div>}>
        <Dashboard />
      </Suspense>
    </div>
  );
}

Module Federation 的優點:

  • Webpack 5 原生支援,設定相對簡單
  • 共享 runtime 和 vendor bundle,載入效能比傳統 iframe 方案好
  • React、Vue、Svelte 元件都可以暴露

Module Federation 的缺點:

  • 理論上 framework-agnostic,實際上 React 的生態系支援最完整
  • 共享的依賴版本必須相容(React 18 vs React 17 可能踩坑)
  • Debug 複雜——問題可能出在 Host、Remote 或兩者的介面定義上

方案二:Single-SPA

定位:一個「路由級」的微前端框架,讓多個子應用在同一個 DOM 樹上共存,URL 決定哪個子應用被激活。

Single-SPA 的模型是:每個子應用都是一個完整的 SPA,退出和激活由框架管理

// 主應用的 index.html
<script type="system-importmap">
{
  "imports": {
    "react": "https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js",
    "vue": "https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js",
    "app1": "http://localhost:8081/app1.js",
    "app2": "http://localhost:8082/app2.js"
  }
}
</script>
// 主應用(root-config.js)
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@org/app1',         // 子應用名稱
  app: () => System.import('app1'), // 載入方式
  activeWhen: ['/app1'],     // URL 前綴匹配時激活
  customProps: {
    // 傳給子應用的全域 props
    authToken: 'xxx',
  },
});

registerApplication({
  name: '@org/app2',
  app: () => System.import('app2'),
  activeWhen: ['/app2'],
});

start();
// 子應用(app1)的寫法
import { mount, unmount } from '@org/app1/single-spa'; // 或框架adaptor

export function bootstrap(props) {
  // 初始化,只跑一次
  return Promise.resolve();
}

export function mount(props) {
  // 渲染到 DOM
  document.getElementById('app1-root').innerHTML = '<h1>App 1 Mounted!</h1>';
  return Promise.resolve();
}

export function unmount(props) {
  // 清理,組件卸載
  return Promise.resolve();
}

Single-SPA 的優點:

  • 完全 framework-agnostic——React、Vue、Angular、Preact、Svelte 都可以用
  • 概念簡單:每個子應用只需要 export 五個 lifecycle 函數
  • URL routing 由框架統一管理

Single-SPA 的缺點:

  • 子應用之間共享狀態需要另外處理(Event Bus、Redux shared store 等)
  • 每個子應用都需要獨立的 build 配置(沒有 Module Federation 的 shared runtime)
  • CSS 是最大的痛點:全域樣式衝突需要嚴格的 naming convention 或 CSS-in-JS

方案三:Web Components(Custom Elements)

定位:利用瀏覽器原生標準,讓任何框架的元件封裝成 Custom Elements,天然跨框架。

<!-- 主應用:直接使用 Custom Elements,無需知道內部用什麼框架 -->
<product-card name="TypeScript 6.0" price="300"></product-card>
// 子應用:封裝成 Web Component
// 任何框架都可以這麼做(React / Vue / Svelte 都支援輸出 Custom Elements)
import { defineCustomElement } from 'vue';
import ProductCard from './ProductCard.vue';

// Vue 3 的 Custom Elements 適配
defineCustomElement(ProductCard, {
  // 定義 props 映射
  props: {
    name: String,
    price: Number,
  },
}).define('product-card');
// 另一個用 React 寫的同功能元件,同樣的 HTML 使用方式
class ProductCardReact extends HTMLElement {
  constructor() {
    super();
    this._root = this.attachShadow({ mode: 'closed' });
  }

  static get observedAttributes() {
    return ['name', 'price'];
  }

  connectedCallback() {
    this._render();
  }

  _render() {
    this._root.innerHTML = `
      <style>
        .card { border: 1px solid #e2e8f0; padding: 1rem; border-radius: 8px; }
        .name { font-weight: bold; }
        .price { color: #059669; }
      </style>
      <div class="card">
        <div class="name">${this.getAttribute('name')}</div>
        <div class="price">NT$ ${this.getAttribute('price')}</div>
      </div>
    `;
  }
}

customElements.define('product-card', ProductCardReact);

Web Components 的優點:

  • 真正意義上的跨框架——任何地方都能用
  • 瀏覽器原生支援,無需 runtime
  • Shadow DOM 提供完美的樣式封裝

Web Components 的缺點:

  • Styling 隔離在 Shadow DOM 裡,與外部 CSS 的整合(特別是 design token)比較麻煩
  • Server-Side Rendering(SSR)支援仍是痛點
  • Framework 對 Custom Elements 的支援程度不一,Angular 的支援尤其複雜

三種方案横向比較

維度Module FederationSingle-SPAWeb Components
Runtime 成本低(共享 runtime)中等(各子應有自己的 bundle)極低(瀏覽器原生)
Framework 支援React 最優,Vue/Svelte 可行全部全部
樣式隔離需要 CSS Modules / naming需要命名規範或 CSS-in-JSShadow DOM 天然隔離
SSR 支援需要額外設定困難非常困難
學習曲線中等中等
團隊自主性最高
適合規模中大型(多框架混合)中型(單框架過渡)大型(跨組織分享)

遷移策略:如何從 Monolithic 走向微前端

遷移微前端不是一次重寫,而是一個漸進的過程。以下是實務上最常成功的策略。

策略一:New == Micro(新增就用微前端)

最保守的起點:新做的功能,直接用微前端架構,老系統維持現狀,兩者通過 iframe 或 REST API 整合。

好處:

  • 不動老系統,零風險
  • 團隊可以先熟悉微前端的開發流程
  • 逐漸建立基礎設施(子應用模板、部署流程、CICD)
<!-- 老系統:index.html -->
<iframe src="http://localhost:3001" style="border: none; width: 100%; height: 600px;"></iframe>

缺點:

  • iframe 的效能差(重複下載 vendor、无法共享狀態)
  • 只適合完全獨立的子系統

策略二:漸進式拆解(Strangler Fig Pattern)

這是實務上最被推薦的做法,用 MVC 時代 Strangler Fig 應用程式的手法:

Step 1:在 Monolithic 裡找出邊界最清晰的子系統
Step 2:用 Module Federation / Single-SPA 把它抽出來,作為 Remote/子應用
Step 3:舊系統透過介面載入 Remote,驗證功能完整
Step 4:老系統中對應的程式碼標記為 Deprecated(不刪除)
Step 5:等 Remote 穩定,確認沒有問題,再刪除老程式碼
Step 6:重複 Step 1-5
// 過渡期的判斷:本地還是 Remote?
// 有兩種做法:

// 做法 A:Feature Flag
const useMicro = featureFlags.enableMicroFrontends;
if (useMicro) {
  const RemoteDashboard = React.lazy(() => import('dashboard/Dashboard'));
} else {
  // 舊的 monolithic import
  import Dashboard from './monolithic/Dashboard';
}

// 做法 B:URL 切換(推薦)
// NGINX 設定:特定路徑的流量打到子應用
location /dashboard/ {
  proxy_pass http://localhost:3001/;
}

策略三:建立微前端底層平台

當你的團隊有 3 個以上的微前端子系統,就應該建立一個共享的「Shell Template」:

# 團隊共享的微前端起始模板
npx create-mf-app@latest my-team-app --template react-ts

這個 Template 應該包含:

  • 統一的 Module Federation / Single-SPA 設定
  • 共用的 UI 設計系統(如你們有自己的 component library)
  • 共享的 auth 邏輯
  • 統一的 Error Boundary 處理
  • 共享的 logging / monitoring 接入

管理上的挑戰:技術之外的事

遷移微前端最大的挑戰,往往不在技術,而是組織和人

誰擁有哪個子應用?

每個子應用需要一個明確的「擁有團隊」。如果一個子應用沒有人願意負責,它很快就會變成另一個老舊 monolith。

## 子應用所有權(範例)

| 子應用 | 擁有團隊 | 部署頻率 | SLO |
|--------|----------|----------|-----|
| @org/shell | Platform | 隨時 | 99.9% uptime |
| @org/dashboard | Data Team | 每週 | 99% uptime |
| @org/checkout | Commerce | 每日 | 99.9% uptime |
| @org/marketing | Growth | 隨時 | 99% uptime |

共享依賴的版本策略

Module Federation 最常見的坑:共享依賴版本不一致。當 Remote 用 React 18、Host 用 React 17,兩個 React runtime 之間的狀態管理會出現不可預期的行為。

解法:建立 Company-wide 的 sharedDependencies.json,強制所有子應用使用相同的版本:

{
  "shared": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "zustand": "^4.4.0",
    "date-fns": "^3.0.0"
  }
}

UI/UX 一致性

每個團隊自己做的元件,最後會造成同一個應用裡有多套按鈕、不同的 spacing、不同的 colors。沒有 Design System 的團隊,拆成微前端後的使用者體驗會大幅下降。

在遷移之前,先確保你們有一個共用的 Design Token 系統(CSS Variables、Style Dictionary),所有子應用都從這裡取用樣式。


總結:何時選哪個方案

選 Module Federation

  • 你是 Webpack 5 專案(大多數 React 專案都是)
  • 團隊想要最大程度的技術自主性
  • 你有多個框架的子系統需要共存
  • 你願意投資在共享依賴版本管理

選 Single-SPA

  • 你正在從一個大 monolithic SPA 遷移出來
  • 你想要一個單純的路由級解決方案
  • 你的團隊規模在 3-10 個子應用左右

選 Web Components

  • 你在多個不同組織之間共享元件
  • 你的團隊完全沒有統一的技術棧
  • 你需要最強的樣式隔離

不要選微前端

  • 你的前端 code < 30 萬行
  • 你的團隊 < 5 個人
  • 你沒有辦法建立跨團隊的 Design System
  • 你的組織還沒有成熟的 CI/CD 和自動化測試

延伸閱讀


微前端是一個組織複雜度管理工具,而不是技術複雜度解決方案。當你的組織準備好承受微前端帶來的額外複雜度時,它會是一個強大的架構選擇。