想象一个仓库,起初是一个整洁的 Angular 应用,供二十名开发者使用,三年后有五十多名工程师每天往里提交代码。每个拉取请求都触碰同一个 bundle。结账流程上的一个 bug 修复意外破坏了引导向导,因为两者都 import 了一个共享工具,而某个人悄悄改了它。发布日历看起来像一场俄罗斯方块游戏——五个团队排队等待把各自的功能塞进唯一一个计划好的部署窗口。所有人都在等所有人,没有人以想要的速度发布。
这就是让前端架构师开始低声说起微前端的痛苦。这个想法听起来很诱人:如果微服务让后端团队从单体中解脱,为什么我们不能对 UI 做同样的事?给每个团队自己独立可部署的前端切片,让浏览器(或服务器)把它们拼回一个无缝的产品。
但这个类比是双刃剑。微服务带来了真正的自由——也带来了真正的运维痛苦。微前端没有什么不同。作为一个曾经在多团队公司落地 Angular 宿主加 React 功能模块的人,我想给你诚实的版本:什么真正有效,什么很伤,以及你在开始拆分之前应该回答的那些问题。
微前端是什么
这个术语在 2016–2017 年前后流行起来,当时团队注意到微服务革命还没有到达浏览器。在经典的单页应用中,一个单体前端 bundle 拥有一切:路由、状态、每个组件、每个团队的功能。微前端把同样的分解思路应用到 UI 层。
微前端是一个独立可构建、独立可部署的 UI 模块,由一个团队端到端拥有。该团队控制自己的仓库(或至少自己的构建流水线)、自己的发布节奏,以及在约定边界内的技术选择。在运行时,一个壳应用——有时叫宿主或容器——把这些模块组合成用户看到的统一产品。
关键转变是所有权。在有共享代码的 monorepo 中,对共享工具的变更可能破坏任何团队的功能;协调是持续的。有了微前端,Team A 部署结账模块不需要 Team B 仪表盘团队的签字。他们之间的契约是约定好的集成接口,而不是共享源代码。
集成方式
“微前端”是一系列技术的总称,而不是一种单一技术。你如何把各个部分拼合在一起,对团队自治、性能和运维复杂性有着巨大影响。以下是五种主要方式:
| 方式 | 工作原理 | 关键权衡 |
|---|---|---|
| 构建时集成(npm 包) | 每个微前端发布为一个有版本的 npm 包。壳应用将它们作为依赖安装并一起打包。 | 设置简单,但团队必须协调发布——更新远端意味着壳应用需要重新部署。并不真正独立。 |
| 服务端组合 | 反向代理或边缘层(如 Nginx、Next.js 边缘函数、Zalando 的 Tailor)在响应到达浏览器之前从不同服务获取 HTML 片段并将其组合。 | 对 SEO 和首次绘制效果好;在服务之间增加了基础设施复杂性和新的延迟面。 |
| 运行时 Module Federation | Webpack 5(或 Rspack/Vite 等效实现)允许构建在运行时从独立的 CDN URL 加载模块,无需重新打包。 | 真正的独立性——远端部署不需要触碰壳应用——但版本偏斜、共享依赖协商和调试工具链很难。 |
| iframe | 每个微前端运行在自己的 iframe 中。隔离几乎完美;通信通过 postMessage 实现。 |
隔离和安全边界非常扎实,但 UX 很差(滚动、焦点、深链接、无障碍都需要自定义管道);感觉有些过时。 |
| Web Components | 每个微前端暴露一个自定义 HTML 元素(<checkout-app>)。壳应用把元素放入页面;浏览器处理生命周期。 |
设计上框架无关,但 SSR 支持不成熟,复杂的共享状态仍然需要全局事件总线或上下文方案。 |
在实践中,Module Federation 已经成为想要真正运行时独立性的大型团队的主流选择。Webpack 5 的插件已经被移植到 Rspack,也有 Vite 的等效实现(如 @originjs/vite-plugin-federation)。今天当有人说“我们在做微前端”,他们几乎总是在指 Module Federation。
如果你的应用必须在搜索结果中排名,而你第一次接触微前端,服务端组合在浏览器触碰之前给你最干净的 HTML。Module Federation 是大招,但在它真正回报之前需要更成熟的部署故事。
为什么团队会采用它们
走向微前端的动机在我见过的公司中是一致的。它们几乎总是追溯到以下四种压力之一:
- 独立部署。被引用最多的单一原因。当十五个团队共享一个前端构建时,部署就是协调税——所有人等所有人的工作通过 QA。微前端让 Team A 在周二发布,而无需等待 Team B 半成品的功能准备好。发布频率上升;每次发布的爆炸半径下降。
- 团队自治和所有权。一个能够端到端构建、测试和部署自己切片的团队移动得更快,感觉更有责任感。当前端不再是所有人都会绊倒的共享公地时,“全栈所有权”才变得真实。
- 渐进式技术迁移。这一点被低估了。如果你在运行一个五年的 AngularJS 应用并想迁移到 React,你不能一次性重写整个东西。微前端让你每次用一个新的 React 模块替换一个路由,而 AngularJS 应用的其余部分继续运行。这是应用于前端的绞杀者无花果模式。
- 故障隔离。结账微前端中的 JavaScript 运行时错误让结账模块崩溃,而不是整个应用。壳应用可以捕获错误边界,显示降级内容,让用户导航离开。在单体中,未捕获的错误经常会撕裂整个页面。
真实的代价
很多博客文章在这里悄然沉默。微前端带来真实的账单,你应该在签约之前看清楚。
Bundle 和依赖重复。如果三个微前端各自打包自己的 React 副本,用户就下载了三遍 React。Module Federation 的共享作用域机制可以在运行时去重,但跨团队正确配置它——尤其是当版本漂移时——并不简单。一个粗心的微前端架构实际上可能比一个优化良好的单体增大你的 payload。
共享设计系统。统一的外观和感觉需要一个共享组件库。如果该库在构建时使用(npm),每个更新它的微前端都必须重新部署。如果通过 Module Federation 在运行时使用,你需要一个版本化和兼容性策略。不管哪种方式,设计系统都成为一个跨团队协调点——而这恰恰是你试图消除的东西。
全局状态和跨 MFE 通信。微前端应该是独立的,但用户是整体性的。壳应用中的通知徽章需要知道结账模块中的一个操作。一个模块中的认证事件必须传播到所有地方。解决方案从共享 Redux store(紧耦合)到自定义事件总线或全局 window 上的共享上下文对象——所有这些都是需要规范才能安全使用的陷阱。
版本偏斜。因为团队独立部署,在任何给定时刻,壳应用可能在运行一个期待 Module Federation 远端入口格式 v1 的版本,而 Team C 最新部署发出的是格式 v2。这些不匹配以神秘的运行时错误的形式出现,本地很难复现,在生产中更难追踪。你需要明确的兼容性契约和优雅降级策略。
运行时性能。多个异步 bundle 加载、运行时模块协商、远端入口的瀑布式获取——微前端引入了单体(所有东西都静态打包)根本没有的延迟。仔细的预加载、CDN 策略和 HTTP/2 推送都有帮助,但它们需要主动投入。
更重的运维和可观测性。你现在有 N 条部署流水线、N 套 CDN 缓存清除规则、N 个构建配置,以及在跨模块边界出错时需要关联的 N 套错误日志。DevOps 开销是真实的。在上线之前,你需要分布式追踪、包含微前端版本信息的结构化日志,以及一个共享的告警仪表板。
把单体拆分成独立的部分并不会减少总复杂性——它把复杂性从源代码移到了集成层、运维工具链和跨团队契约中。如果你的组织还没有准备好投资那个运维基础设施,你最终会得到一个分布式单体:单体的所有耦合,加上分布式系统的所有运维开销。
落地 Angular 宿主 + React 模块:诚实的亲历记
几年前,我所在的团队把一个大型 Angular 平台——数十万行代码,五个产品小队,季度发布节奏感觉更像人质谈判而不是产品流程——迁移到了 Module Federation 架构:一个 Angular 壳宿主托管独立部署的 React 功能模块。
以下是真正有效的地方。
- 独立部署节奏改变了文化。两个月内,历史上按季度发布的小队开始每周发布。反馈循环压缩了。工程师开始更关注可观测性,因为他们自己就是在自己的部署上线后盯着仪表盘的人。
- 功能隔离大幅降低了回归范围。对 billing 模块的变更不再有理论上破坏用户设置页面的路径。每个团队的回归测试套件范围缩小了;集成测试专注于契约点,而不是整个应用。
- 渐进式迁移是救命稻草。我们没有经历六个月的“大爆炸”重写冻结期。React 模块在十八个月内一次替换一个 Angular 视图,而产品持续发布。业务利益相关方几乎没有注意到这次过渡——这正是你想要的。
以下是很痛的地方。
- 共享状态始终是个头疼问题。我们最终在全局 window 对象上建立了一个自定义事件总线——功能上可以,但很脆弱。当两个团队独立更改了事件 payload 的结构,我们得到了只在特定导航序列中出现的静默 bug。我们最终把跨 MFE 事件建模成内部 API,有一个 schema 注册表,这大大有帮助,但花了数周时间构建。
- 重复依赖膨胀了 bundle。在 Angular 和 React 两个不同构建系统之间把 Module Federation 的
shared配置做对需要付出真正的努力。有几个冲刺,用户在我们发现之前都在下载两份 RxJS 和几乎两份图表库。CI 中的自动化 bundle 分析变得必不可少。 - 版本协调从未完全消失。壳的 Angular 版本和 React 模块的依赖矩阵必须保持松散兼容。当 Angular 发布重大更新时,我们不能仅仅升级壳;我们必须先审计每个模块的 peer 依赖。协调成本比单体时低,但并没有消失。
综合判断:对于那个组织在那个规模下,这个架构是正确的选择。但说实话,我们低估了运维投入大约百分之三十,共享状态问题花了两倍于预期的时间才干净地解决。去的时候要睁大眼睛。
什么时候不该用微前端
微前端不适合你的情况,最清晰的迹象是前端痛苦的主要来源根本不是团队规模协调问题——而是完全别的东西。
- 只有一个小团队。如果前端的每个人都能进入一个视频通话,你没有协调问题。你有一个需要组织的代码库。微前端会给你所有运维开销,而没有任何团队自治的好处。改用有良好强制模块边界的模块化单体前端。
- 一个小型应用。如果整个前端只有几个路由和几千行代码,拆分它创造的基础设施面积比产品本身还大。Module Federation 配置和多流水线 CD 的开销远超任何好处。
- 当一个有良好边界的 monorepo 就够用时。Nx、Turborepo 和类似工具让你执行严格的模块边界、只对受影响的包运行构建,并给团队一定程度的所有权——没有运行时组合的复杂性。对于很多在 20–80 名工程师范围内的公司,一个结构良好的 monorepo 是更好的权衡。而且重构起来也容易得多。
- 当你的团队还没有准备好基础知识时。如果你的 CI/CD 流水线不可靠,可观测性故事很薄,或者有未解决的跨团队沟通问题,增加微前端的复杂性会放大所有这些问题。先修好地基。
这与后端决策密切类似。不是每个后端都应该是一队微服务——同样的道理在这里也适用。如果你还没有读过单体 → 微服务的拆解,它在拆分后端之前提出的那些问题几乎逐字适用于前端:你有独立的扩展需求、独立的故障域和真正自治的团队吗?如果没有,单体——或模块化单体——可能是更好的答案。
一个有严格 lint 边界、每个团队拥有的包和快速增量构建系统的前端 monorepo,能以运维成本的一小部分给你 70–80% 的自治好处。在采用 Module Federation 之前,先问问更清晰的模块边界是否能解决实际问题。
按公司规模
这个决策最有用的视角之一就是:你有多少前端工程师,他们的团队需要多独立地运作?
| 组织规模 | 典型前端团队结构 | 建议 | 真实场景模式 |
|---|---|---|---|
| 小型(< 15 名前端开发者) | 一两个小队,每个人都了解彼此的代码。 | 不要用。用模块化单体或结构良好的 monorepo。微前端的协调开销超过了好处。 | 早期 SaaS 初创公司:带功能文件夹和严格 import 的 Next.js 应用——发布快,易于推理。 |
| 中型(15–60 名前端开发者) | 三到八个小队,开始感到部署竞争和所有权摩擦。 | 也许,在边界处。考虑只在摩擦最高的边界提取——比如结账、报告仪表盘——而不是拆分所有东西。带构建缓存的 monorepo 可能先解决它。 | 规模化中的电商:大多数功能用 monorepo,只有支付流程用 Module Federation 远端(PCI 隔离团队,独立部署)。 |
| 大型 / 企业级(60+ 名前端开发者) | 很多小队,通常跨时区,有硬性的团队所有权边界和独立路线图。 | 通常有充分理由。共享部署的协调开销是真实的、可量化的。由专职平台团队支持的 Module Federation 或服务端组合是合理的投入。 | 大型银行、电信公司、全球零售商:由平台团队拥有的壳应用;数十个由功能部落拥有的微前端,每个都有自己的 CD 流水线和版本契约。 |
Spotify 的工程博客记录了他们的“小队模型”以及伴随而来的前端架构决策。Zalando 公开写过他们的Tailor服务端组合方案。IKEA 的数字平台团队讨论过规模化的 Module Federation。这些公司的共同点不是技术——而是规模:有大量自治团队,他们有真正独立的路线图,否则每天都会相互阻塞。
在另一个极端,我见过的一些在 20–50 名开发者范围内最高效的前端组织,在单个 Nx monorepo 中运行所有东西,有严格强制的模块边界和快速的增量 CI。他们获得快速构建、清晰所有权和零运行时组合 bug——因为根本没有运行时组合。他们每六个月重新审视一次微前端问题,随着公司成长,这正是应该做的。
核心要点
- 微前端把微服务的分解思路应用于 UI:独立可构建、独立可部署的前端切片,每个由一个团队拥有,在运行时(或构建时,或服务端)组合成一个产品。
- Module Federation(Webpack 5 / Rspack / Vite)是主流的运行时方案——它提供真正的部署独立性,但需要仔细的共享依赖配置和版本契约管理。
- 核心好处是部署独立性、团队自治、渐进式框架迁移和故障隔离。如果你在规模上不真正需要这些,你在支付代价却没有回报。
- 真实的代价是 bundle 重复、共享设计系统治理、跨 MFE 状态复杂性、版本偏斜、运行时延迟和更重的 DevOps。这些都不是致命的——但都需要主动投入。
- 不要使用微前端如果你有一个小团队、一个小型应用,或者一个结构良好的 monorepo 能解决实际摩擦的话。模块化单体被低估了。
- 让架构与团队拓扑匹配:小团队 → 模块化单体;中型 → monorepo 或外科式 MFE 拆分;大型企业 → 由专职平台团队支持的完整微前端平台。
- 复杂性总是去某个地方。微前端把它从源代码移到了集成契约和运维基础设施。在你承诺之前,确保你的组织准备好拥有这次转变。
本文是关于前后端架构系列的一部分。如果你是先看到这篇,我鼓励你读回系列开头——Ports & Adapters——那里讲解了六边形架构模式,以及如何让你的核心业务逻辑保持干净和可测试,无论它周围坐着的是什么框架或交付机制。这些思想是互补的:在单个后端服务内部建立清晰边界的那种思维,以更大的粒度适用于前端团队之间的边界。更多架构文章即将到来——欢迎关注,期待与你相见。