Micro-Frontend 2026: Chia để trị Frontend với Module Federation 2.0

Posted on: 4/18/2026 5:13:44 AM

Table of contents

  1. 1. Bài toán Scale Frontend — Khi Monolith Frontend trở thành nút thắt
    1. Monolith Frontend ≠ Bad Architecture
  2. 2. Micro-Frontend — Chia để trị, nhưng đúng cách
    1. 2.1. Năm nguyên tắc cốt lõi
  3. 3. Các mô hình tích hợp Micro-Frontend
    1. Runtime Integration là xu hướng 2026
  4. 4. Module Federation 2.0 — Deep Dive
    1. 4.1. Decoupled Runtime — Không còn phụ thuộc Webpack
    2. 4.2. Dynamic Type Hints — TypeScript không cần build lại
    3. 4.3. Runtime Plugins — Mở rộng hành vi tại runtime
  5. 5. Rspack — Build 10x nhanh hơn Webpack
    1. 5.1. Cấu hình Rspack + Module Federation 2.0
  6. 6. Thực hành: Micro-Frontend với Vue 3 + Rspack
    1. 6.1. Shell App — Orchestrator trung tâm
      1. ErrorBoundary là bắt buộc
    2. 6.2. Remote App — Chạy độc lập và như micro-frontend
  7. 7. Giao tiếp giữa các Micro-Frontend
    1. 7.1. Custom Events — Đơn giản và hiệu quả
    2. 7.2. Shared API Layer — Cho dữ liệu phức tạp
      1. Anti-pattern: Shared Pinia Store
  8. 8. Chiến lược Shared Dependencies
  9. 9. CI/CD và Deployment Strategy
    1. 9.1. Versioning Strategy
    2. 9.2. Canary Deployment với MF Runtime Plugin
  10. 10. CSS Isolation — Tránh style conflict
    1. Khuyến nghị 2026: CSS Modules + Design Token
  11. 11. Performance — Tránh Micro-Frontend trở thành Macro-Problem
    1. 11.1. Preloading Remote Modules
    2. 11.2. Islands Architecture — Chỉ hydrate khi cần
  12. 12. Khi nào nên (và không nên) dùng Micro-Frontend
    1. Dấu hiệu nên áp dụng
    2. Anti-patterns cần tránh
      1. Micro-Frontend Tax
  13. Tổng kết
  14. Tài liệu tham khảo

Khi frontend của bạn vượt quá 200.000 dòng code, 15 developer cùng commit vào một repo, và mỗi lần build mất 8 phút — bạn đang đối mặt với bài toán scale frontend. Micro-Frontend không phải buzzword mới, nhưng với Module Federation 2.0 đạt stable release tháng 4/2026 và Rspack cho tốc độ build nhanh gấp 10 lần Webpack, kiến trúc này cuối cùng đã sẵn sàng cho production thực sự.

40% Giảm production incidents khi deploy độc lập
3x Tỷ lệ áp dụng ở team 10+ frontend engineers
10x Rspack build nhanh hơn Webpack
6 Bundler hỗ trợ Module Federation 2.0

1. Bài toán Scale Frontend — Khi Monolith Frontend trở thành nút thắt

Hãy tưởng tượng một ứng dụng SaaS với các module: Dashboard, User Management, Billing, Analytics, Notification Center. Tất cả nằm trong một repo Vue/React duy nhất. Ban đầu mọi thứ ổn, nhưng khi team mở rộng:

  • Merge conflict liên tục: 5 team cùng sửa package.json, shared components, và router config
  • Build time phi tuyến: thêm 1 module mới khiến Webpack build chậm thêm 30% vì phải resolve lại toàn bộ dependency graph
  • Deploy coupling: team Billing fix 1 bug nhỏ → phải deploy lại toàn bộ app 500K dòng code, ảnh hưởng Dashboard và Analytics
  • Technology lock-in: muốn migrate từ Vue 2 sang Vue 3 phải làm "big bang" — không thể migrate từng module

Monolith Frontend ≠ Bad Architecture

Nếu team bạn dưới 5 người và app dưới 50K LOC, monolith frontend vẫn là lựa chọn tốt nhất. Micro-Frontend giải quyết vấn đề tổ chức team (Conway's Law) chứ không phải vấn đề kỹ thuật đơn thuần. Đừng over-engineer khi chưa cần.

2. Micro-Frontend — Chia để trị, nhưng đúng cách

Micro-Frontend là kiến trúc chia một ứng dụng web lớn thành nhiều ứng dụng nhỏ hơn (gọi là remote hoặc fragment), mỗi ứng dụng được phát triển, test và deploy độc lập bởi một team riêng biệt, sau đó được tổ hợp (compose) thành một trải nghiệm người dùng thống nhất.

graph TB
    subgraph "Shell / Host App"
        Shell["🏠 Shell Application
Router + Layout + Auth"] end subgraph "Team Products" MF1["📦 Product Catalog
Vue 3.6 + Rspack"] end subgraph "Team Checkout" MF2["🛒 Checkout Flow
Vue 3.6 + Vite"] end subgraph "Team Account" MF3["👤 User Account
React 19"] end subgraph "Shared" DS["🎨 Design System"] Auth["🔐 Auth Module"] end Shell --> MF1 Shell --> MF2 Shell --> MF3 MF1 --> DS MF2 --> DS MF3 --> DS MF1 --> Auth MF2 --> Auth MF3 --> Auth style Shell fill:#e94560,stroke:#fff,color:#fff style MF1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style MF2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style MF3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style DS fill:#2c3e50,stroke:#fff,color:#fff style Auth fill:#2c3e50,stroke:#fff,color:#fff

Kiến trúc Micro-Frontend: Shell App orchestrate các remote app độc lập

2.1. Năm nguyên tắc cốt lõi

  1. Team Autonomy: mỗi team sở hữu toàn bộ lifecycle từ dev → test → deploy
  2. Technology Agnostic: team A dùng Vue, team B dùng React — không ép buộc
  3. Independent Deployment: deploy module Billing không ảnh hưởng Dashboard
  4. No Shared State: các micro-frontend giao tiếp qua contract rõ ràng, không chia sẻ global state
  5. Resilient by Default: module Analytics crash không kéo sập toàn bộ app

3. Các mô hình tích hợp Micro-Frontend

Có 4 cách chính để tổ hợp các micro-frontend thành một ứng dụng thống nhất:

Mô hìnhCách hoạt độngƯu điểmNhược điểmPhù hợp khi
Build-time (npm packages)Mỗi MF là npm package, host app install và build cùngĐơn giản, type-safeDeploy coupling — update 1 MF phải rebuild hostTeam nhỏ, ít thay đổi
Runtime — Module FederationHost load remote bundle qua HTTP tại runtimeDeploy độc lập, shared depsPhức tạp hơn, cần version strategyTeam lớn, deploy thường xuyên
Server-Side CompositionServer stitches HTML fragments từ nhiều serviceSEO tốt, fast TTFBKhó interactive, phức tạp infraContent-heavy sites, e-commerce
iframeMỗi MF chạy trong iframe riêngIsolation hoàn toànPerformance kém, UX rời rạcLegacy integration,

Runtime Integration là xu hướng 2026

Với Module Federation 2.0 đạt stable, runtime integration đã vượt qua các hạn chế cũ (thiếu type safety, version conflict). Hầu hết các dự án mới trong 2026 nên chọn Module Federation làm điểm xuất phát.

4. Module Federation 2.0 — Deep Dive

Module Federation ban đầu là tính năng của Webpack 5 (2020), cho phép một JavaScript application load code từ application khác tại runtime. Phiên bản 2.0 (stable tháng 4/2026) là bước tiến lớn với 3 thay đổi mang tính cách mạng:

4.1. Decoupled Runtime — Không còn phụ thuộc Webpack

MF 2.0 tách hoàn toàn runtime khỏi bundler. Runtime layer trở thành một module độc lập, chạy nhất quán trên bất kỳ build tool nào:

graph LR
    subgraph "Module Federation 2.0 Runtime"
        RT["MF Runtime
(Bundler-agnostic)"] end subgraph "Build Tools" WP["Webpack 5"] RS["Rspack"] VI["Vite"] RU["Rollup / Rolldown"] RB["Rsbuild"] end subgraph "Frameworks" NX["Next.js"] NU["Nuxt 4"] MJ["Modern.js"] end WP --> RT RS --> RT VI --> RT RU --> RT RB --> RT RT --> NX RT --> NU RT --> MJ style RT fill:#e94560,stroke:#fff,color:#fff style WP fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50 style RS fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50 style VI fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50 style RU fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50 style RB fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50

MF 2.0 Runtime hoạt động độc lập với build tool — team có thể dùng Webpack, Rspack hay Vite tùy ý

Điều này giải quyết vấn đề lớn nhất của MF 1.0: mọi remote app phải dùng chung Webpack. Với MF 2.0, host app dùng Rspack và remote app dùng Vite vẫn hoạt động hoàn hảo.

4.2. Dynamic Type Hints — TypeScript không cần build lại

MF 2.0 tự động generate và load TypeScript types từ remote modules tại development time, cho trải nghiệm tương tự npm link nhưng không cần link:

// host-app/src/bootstrap.ts
// Types từ remote "checkout" được tự động generate
import { CartSummary } from 'checkout/CartSummary';
//                         ↑ Full type inference, autocomplete, go-to-definition

// Khi remote app update interface → types tự hot-reload
// Không cần publish npm package, không cần rebuild host

Cơ chế hoạt động: MF 2.0 plugin chạy một background process, watch remote manifest, tự generate .d.ts vào node_modules/@mf-types/. Developer thấy type error ngay khi remote thay đổi interface — phát hiện breaking change trước khi deploy.

4.3. Runtime Plugins — Mở rộng hành vi tại runtime

MF 2.0 giới thiệu hệ thống plugin cho phép can thiệp vào quá trình load remote module:

import { FederationRuntimePlugin } from '@module-federation/runtime';

const AuthPlugin: () => FederationRuntimePlugin = () => ({
  name: 'auth-plugin',
  // Gắn auth token vào mọi request load remote
  fetch(url, options) {
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${getAccessToken()}`
      }
    });
  },
  // Fallback khi remote không khả dụng
  errorLoadRemote({ id, error }) {
    console.error(`Failed to load ${id}:`, error);
    return () => import('./fallback/MaintenancePage.vue');
  }
});

Plugin system cho phép: retry logic, A/B testing (load remote version A hoặc B), canary deployment, telemetry tracking, và circuit breaker pattern.

5. Rspack — Build 10x nhanh hơn Webpack

Một trong những rào cản lớn nhất của Micro-Frontend là developer experience. Khi phải chạy 5 remote app + 1 host app cùng lúc, mỗi app build 30 giây bằng Webpack → tốn 3 phút chỉ để start dev environment. Rspack (viết bằng Rust, do ByteDance phát triển) giải quyết triệt để vấn đề này.

5-10x Nhanh hơn Webpack cold start
95% Tương thích Webpack config
Rust Core engine cho parallel processing
1st class Module Federation support

Rspack không phải bundler hoàn toàn mới — nó tương thích gần như hoàn toàn với Webpack config, plugins, và loaders. Việc migrate từ Webpack sang Rspack thường chỉ mất vài giờ thay vì vài tuần.

5.1. Cấu hình Rspack + Module Federation 2.0

// rspack.config.js — Host App
const { ModuleFederationPlugin } = require('@module-federation/enhanced/rspack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        // Remote apps — URL trỏ tới manifest, không phải bundle
        productCatalog: 'productCatalog@https://product.example.com/mf-manifest.json',
        checkout: 'checkout@https://checkout.example.com/mf-manifest.json',
        userAccount: 'userAccount@https://account.example.com/mf-manifest.json',
      },
      shared: {
        vue: { singleton: true, requiredVersion: '^3.6.0' },
        pinia: { singleton: true, requiredVersion: '^3.0.0' },
        'vue-router': { singleton: true, requiredVersion: '^4.5.0' },
      },
    }),
  ],
};
// rspack.config.js — Remote App (Product Catalog)
const { ModuleFederationPlugin } = require('@module-federation/enhanced/rspack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'productCatalog',
      exposes: {
        // Expose specific components, không phải toàn bộ app
        './ProductList': './src/components/ProductList.vue',
        './ProductDetail': './src/components/ProductDetail.vue',
        './SearchBar': './src/components/SearchBar.vue',
      },
      shared: {
        vue: { singleton: true, requiredVersion: '^3.6.0' },
        pinia: { singleton: true, requiredVersion: '^3.0.0' },
      },
    }),
  ],
};

6. Thực hành: Micro-Frontend với Vue 3 + Rspack

6.1. Shell App — Orchestrator trung tâm

Shell app (hay host app) chịu trách nhiệm: routing cấp cao, layout chung, authentication, và load các remote app theo demand.

// shell/src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('../layouts/MainLayout.vue'),
      children: [
        {
          path: 'products/:pathMatch(.*)*',
          component: () => import('productCatalog/ProductList'),
          //                      ↑ Load từ remote app tại runtime
        },
        {
          path: 'checkout/:pathMatch(.*)*',
          component: () => import('checkout/CheckoutFlow'),
        },
        {
          path: 'account/:pathMatch(.*)*',
          component: () => import('userAccount/AccountDashboard'),
        },
      ],
    },
  ],
});
<!-- shell/src/layouts/MainLayout.vue -->
<template>
  <div class="app-shell">
    <AppHeader />
    <nav>
      <router-link to="/products">Sản phẩm</router-link>
      <router-link to="/checkout">Thanh toán</router-link>
      <router-link to="/account">Tài khoản</router-link>
    </nav>
    <main>
      <ErrorBoundary>
        <Suspense>
          <router-view />
          <template #fallback>
            <LoadingSkeleton />
          </template>
        </Suspense>
      </ErrorBoundary>
    </main>
  </div>
</template>

ErrorBoundary là bắt buộc

Luôn wrap remote component trong ErrorBoundary + Suspense. Khi remote app bị lỗi (network fail, version mismatch), ErrorBoundary hiển thị fallback UI thay vì crash toàn bộ shell app. Đây là nguyên tắc "Resilient by Default" trong thực tế.

6.2. Remote App — Chạy độc lập và như micro-frontend

Mỗi remote app phải hoạt động được ở 2 chế độ: standalone (cho dev/test độc lập) và federated (khi load từ shell):

// product-catalog/src/bootstrap.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';

const mount = (el: string | HTMLElement, opts?: { basePath?: string }) => {
  const app = createApp(App);
  app.use(createPinia());

  if (opts?.basePath) {
    // Federated mode: shell truyền basePath
    router.replace(opts.basePath);
  }

  app.use(router);
  app.mount(typeof el === 'string' ? el : el);
  return app;
};

// Standalone mode: mount trực tiếp
const rootEl = document.getElementById('app');
if (rootEl) {
  mount(rootEl);
}

export { mount };

7. Giao tiếp giữa các Micro-Frontend

Đây là phần phức tạp nhất và cũng dễ mắc sai lầm nhất. Nguyên tắc vàng: các micro-frontend KHÔNG chia sẻ state trực tiếp. Thay vào đó, sử dụng một trong các pattern sau:

7.1. Custom Events — Đơn giản và hiệu quả

// shared-contracts/events.ts (npm package dùng chung)
export interface CartUpdatedEvent {
  type: 'cart:updated';
  payload: { itemCount: number; totalAmount: number };
}

export interface UserLoggedInEvent {
  type: 'user:loggedIn';
  payload: { userId: string; displayName: string };
}

export type MicroFrontendEvent = CartUpdatedEvent | UserLoggedInEvent;

// Product Catalog — publish event
export function addToCart(product: Product) {
  // ... business logic
  window.dispatchEvent(
    new CustomEvent('mf:event', {
      detail: {
        type: 'cart:updated',
        payload: { itemCount: cart.length, totalAmount: total }
      } satisfies CartUpdatedEvent
    })
  );
}

// Shell App — subscribe
window.addEventListener('mf:event', ((e: CustomEvent<MicroFrontendEvent>) => {
  switch (e.detail.type) {
    case 'cart:updated':
      headerCartBadge.value = e.detail.payload.itemCount;
      break;
    case 'user:loggedIn':
      currentUser.value = e.detail.payload;
      break;
  }
}) as EventListener);

7.2. Shared API Layer — Cho dữ liệu phức tạp

Khi cần chia sẻ dữ liệu phức tạp hơn (user session, feature flags, theme), dùng một shared API layer được expose qua Module Federation:

// shell/src/shared/api.ts — expose qua MF
import { reactive, readonly } from 'vue';

const state = reactive({
  user: null as User | null,
  theme: 'light' as 'light' | 'dark',
  featureFlags: {} as Record<string, boolean>,
});

export const shellApi = {
  getUser: () => readonly(state).user,
  getTheme: () => readonly(state).theme,
  getFeatureFlag: (key: string) => state.featureFlags[key] ?? false,
  // Event-based notification khi state thay đổi
  onUserChange: (cb: (user: User | null) => void) => {
    watch(() => state.user, cb);
  },
};

Anti-pattern: Shared Pinia Store

Đừng bao giờ share một Pinia store instance giữa các micro-frontend. Điều này tạo implicit coupling — khi team A thay đổi store schema, team B bị break mà không biết. Luôn giao tiếp qua explicit contract (events hoặc exposed API).

8. Chiến lược Shared Dependencies

Module Federation cho phép các app chia sẻ dependencies thay vì bundle riêng. Nhưng nếu không cấu hình đúng, shared dependencies sẽ trở thành nguồn bug khó debug nhất.

StrategyConfigHành viKhi nào dùng
Singletonsingleton: trueChỉ load 1 version duy nhất, version cao nhất thắngVue, React, Pinia — libraries giữ global state
Eagereager: trueBundle vào initial chunk, không lazy loadLib cần trước khi remote ready (polyfills)
Required VersionrequiredVersion: '^3.6.0'Warning/error nếu version không matchTránh major version conflict
Strict VersionstrictVersion: trueThrow error thay vì warning khi mismatchCritical libs (auth, crypto)
// Cấu hình shared dependencies chuẩn cho Vue ecosystem
shared: {
  // Framework core — PHẢI singleton
  'vue': { singleton: true, requiredVersion: '^3.6.0' },
  'vue-router': { singleton: true, requiredVersion: '^4.5.0' },
  'pinia': { singleton: true, requiredVersion: '^3.0.0' },

  // UI library — singleton để tránh duplicate CSS
  '@company/design-system': { singleton: true },

  // Utility libs — KHÔNG singleton, mỗi app tự bundle
  // lodash, dayjs, axios: để mặc định
}
graph TB
    subgraph "Browser Runtime"
        subgraph "Shared Scope"
            Vue["vue@3.6.2
(singleton)"] Router["vue-router@4.5.1
(singleton)"] DS["Design System@2.1
(singleton)"] end subgraph "Shell Bundle" ShellCode["Shell App Code"] end subgraph "Product Bundle" ProdCode["Product Code"] Lodash1["lodash@4.17
(own copy)"] end subgraph "Checkout Bundle" CheckCode["Checkout Code"] Lodash2["lodash@4.17
(own copy)"] end end ShellCode --> Vue ProdCode --> Vue CheckCode --> Vue ShellCode --> Router ProdCode --> DS CheckCode --> DS style Vue fill:#e94560,stroke:#fff,color:#fff style Router fill:#e94560,stroke:#fff,color:#fff style DS fill:#e94560,stroke:#fff,color:#fff style Lodash1 fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50 style Lodash2 fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50

Shared Scope: Vue và Router load 1 lần, utility libs (lodash) mỗi app bundle riêng

9. CI/CD và Deployment Strategy

Lợi ích lớn nhất của Micro-Frontend là independent deployment. Mỗi team có pipeline riêng, deploy khi sẵn sàng mà không cần coordinate với team khác.

graph LR
    subgraph "Team Product"
        P1["Push to main"] --> P2["Build + Test"]
        P2 --> P3["Deploy to CDN
product.cdn.com"] P3 --> P4["Update manifest"] end subgraph "Team Checkout" C1["Push to main"] --> C2["Build + Test"] C2 --> C3["Deploy to CDN
checkout.cdn.com"] C3 --> C4["Update manifest"] end subgraph "Shell App" S1["Reads manifest
at runtime"] S1 --> S2["Loads latest
remote bundle"] end P4 --> S1 C4 --> S1 style P3 fill:#4CAF50,stroke:#fff,color:#fff style C3 fill:#4CAF50,stroke:#fff,color:#fff style S1 fill:#e94560,stroke:#fff,color:#fff

Independent deployment: mỗi team deploy riêng, shell app tự load version mới nhất qua manifest

9.1. Versioning Strategy

Có 2 cách quản lý version cho remote modules:

Dynamic manifest (khuyến nghị): Remote app cập nhật mf-manifest.json khi deploy. Shell app luôn load manifest mới nhất → tự động nhận version mới. Không cần redeploy shell.

// https://product.example.com/mf-manifest.json
{
  "id": "productCatalog",
  "name": "productCatalog",
  "metaData": {
    "buildHash": "a3f7c2b",
    "version": "2.14.0",
    "buildTime": "2026-04-18T10:30:00Z"
  },
  "exposes": {
    "./ProductList": {
      "assets": { "js": { "async": ["src_components_ProductList_vue.js"] } }
    }
  },
  "shared": [
    { "name": "vue", "version": "3.6.2", "scope": "default" }
  ]
}

Pinned version: Shell config trỏ đến version cụ thể. An toàn hơn nhưng cần update shell config mỗi lần remote deploy. Phù hợp cho môi trường production yêu cầu kiểm soát chặt.

9.2. Canary Deployment với MF Runtime Plugin

// shell/src/plugins/canary.ts
import { FederationRuntimePlugin } from '@module-federation/runtime';

const CanaryPlugin: () => FederationRuntimePlugin = () => ({
  name: 'canary-plugin',
  beforeRequest(args) {
    const { id } = args;
    // 5% traffic → canary version
    if (isInCanaryGroup(getUserId())) {
      args.options.remotes = args.options.remotes.map(remote => {
        if (remote.name === id) {
          return {
            ...remote,
            entry: remote.entry.replace('/stable/', '/canary/'),
          };
        }
        return remote;
      });
    }
    return args;
  },
});

10. CSS Isolation — Tránh style conflict

Khi nhiều micro-frontend cùng render trên 1 page, CSS conflict là vấn đề thường gặp nhất. Các giải pháp:

Giải phápCách hoạt độngProsCons
CSS ModulesClass name được hash unique (.btn_a3f7c)Zero conflict, tree-shakableDynamic class name khó debug
Vue Scoped StylesAttribute selector (.btn[data-v-7ba5bd90])Native Vue, dễ dùngSpecificity vẫn có thể leak
CSS-in-JSStyle inject tại runtimeFull isolationRuntime cost, bundle size tăng
CSS Layers@layer mf-product { ... }Native CSS, rõ ràng priorityCần coordinate layer order
Shadow DOMWeb Component encapsulationComplete isolationKhó theme, performance overhead

Khuyến nghị 2026: CSS Modules + Design Token

Dùng CSS Modules cho isolation, kết hợp Design Tokens (CSS custom properties) từ shared design system cho consistency. Mỗi MF import @company/tokens — chỉ chứa CSS variables, không chứa component styles.

/* @company/tokens/variables.css */
:root {
  --color-primary: #e94560;
  --color-surface: #ffffff;
  --spacing-md: 16px;
  --radius-lg: 12px;
  --font-body: 'Inter', system-ui, sans-serif;
}

/* product-catalog/src/components/ProductCard.module.css */
.card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  padding: var(--spacing-md);
}
.title {
  color: var(--color-primary);
  font-family: var(--font-body);
}

11. Performance — Tránh Micro-Frontend trở thành Macro-Problem

Micro-Frontend nếu không cẩn thận sẽ tăng bundle size tổng (mỗi app bundle riêng), tăng số request (load nhiều manifest + chunk), và giảm INP (hydration overhead). Các chiến lược tối ưu:

11.1. Preloading Remote Modules

// Shell app — preload remote khi user hover vào nav link
import { preloadRemote } from '@module-federation/runtime';

function onNavHover(route: string) {
  switch (route) {
    case '/products':
      preloadRemote([{ name: 'productCatalog', expose: './ProductList' }]);
      break;
    case '/checkout':
      preloadRemote([{ name: 'checkout', expose: './CheckoutFlow' }]);
      break;
  }
}

11.2. Islands Architecture — Chỉ hydrate khi cần

Thay vì hydrate toàn bộ page, chỉ hydrate micro-frontend "island" mà user đang tương tác. Kết hợp với IntersectionObserver để lazy-hydrate khi element vào viewport:

<!-- LazyMicroFrontend.vue -->
<template>
  <div ref="container">
    <component :is="RemoteComponent" v-if="isVisible" />
    <slot v-else name="placeholder">
      <div class="skeleton" />
    </slot>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, defineAsyncComponent, shallowRef } from 'vue';

const props = defineProps<{
  remoteName: string;
  exposedModule: string;
}>();

const container = ref<HTMLElement>();
const isVisible = ref(false);

const RemoteComponent = shallowRef(
  defineAsyncComponent(() => import(`${props.remoteName}/${props.exposedModule}`))
);

onMounted(() => {
  const observer = new IntersectionObserver(
    ([entry]) => {
      if (entry.isIntersecting) {
        isVisible.value = true;
        observer.disconnect();
      }
    },
    { rootMargin: '200px' }
  );
  if (container.value) observer.observe(container.value);
});
</script>

12. Khi nào nên (và không nên) dùng Micro-Frontend

graph TD
    Q1{"Frontend team
≥ 8 người?"} Q1 -->|Không| NO["❌ Giữ Monolith
Micro-FE là overkill"] Q1 -->|Có| Q2{"Nhiều team cùng
deploy 1 app?"} Q2 -->|Không| NO2["❌ Chia repo
nhưng không cần MF"] Q2 -->|Có| Q3{"Deploy frequency
≥ 2 lần/tuần
mỗi team?"} Q3 -->|Không| MAYBE["⚠️ Cân nhắc
Modular Monolith
trước"] Q3 -->|Có| YES["✅ Micro-Frontend
là lựa chọn đúng"] style YES fill:#4CAF50,stroke:#fff,color:#fff style NO fill:#f8f9fa,stroke:#e0e0e0,color:#888 style NO2 fill:#f8f9fa,stroke:#e0e0e0,color:#888 style MAYBE fill:#ff9800,stroke:#fff,color:#fff

Decision tree: Bạn có thực sự cần Micro-Frontend?

Dấu hiệu nên áp dụng

  • Nhiều team (3+) cùng phát triển 1 frontend application
  • Deploy frequency cao — mỗi team cần deploy độc lập ít nhất 2 lần/tuần
  • Cần migrate dần công nghệ (Vue 2 → Vue 3, hoặc Vue → React cho 1 module)
  • App đủ lớn (100K+ LOC) và có boundary rõ ràng giữa các domain

Anti-patterns cần tránh

  • Nano-Frontend: chia quá nhỏ — mỗi component là 1 remote app → overhead lớn hơn lợi ích
  • Shared Everything: share quá nhiều dependencies → quay lại monolith coupling
  • Cross-MF Database Queries: remote app truy cập API/data của remote app khác trực tiếp → tạo distributed monolith
  • Ignoring UX Consistency: mỗi team tự design component → user thấy app "rời rạc"

Micro-Frontend Tax

Micro-Frontend thêm overhead đáng kể: shared dependency management, contract testing giữa teams, CI/CD pipeline phức tạp hơn, monitoring phải track từng remote module. Chỉ "trả thuế" này khi lợi ích từ team autonomy và independent deployment thực sự lớn hơn chi phí.

Tổng kết

Micro-Frontend trong 2026 không còn là thí nghiệm. Với Module Federation 2.0 (stable, bundler-agnostic, dynamic types), Rspack (build 10x nhanh hơn Webpack), và các pattern đã được kiểm chứng tại các enterprise lớn — kiến trúc này đã sẵn sàng cho production. Điều quan trọng nhất vẫn là: đánh giá đúng bài toán trước khi chọn giải pháp. Nếu team bạn đang chật vật với merge conflict, deploy coupling, và build time phi lý trên một monolith frontend lớn — Micro-Frontend với Module Federation 2.0 chính là lối ra.

Tài liệu tham khảo