用户实际看到的是哪个应用?
“我这里页面坏了”
Section titled ““我这里页面坏了””上一篇文章讨论了动态微前端架构中的一个核心问题:
对某个具体使用上下文,应该激活哪一组产品组合?
为此,不同 Remote 可以独立发布,再通过 Composition ID 组合成一组经过批准的产品组合。Host 版本则构成这组 Composition 被批准运行的 Host Context。
从架构角度看,系统可能是:
Host 5.4.2Remote A 1.1.0Remote B 2.3.1Remote C 4.7.0但从用户角度看,只存在:
这个应用用户不会报告:
“Remote B 2.3.1 在 Bootstrap 时失败了。”
他只会说:
“页面不能用了。”
问题也正是在这里发生变化。
仅仅知道激活了哪一组产品 Composition 已经不够。对于一个具体故障,必须能够追溯:这个 Session 实际解析到了哪组 Composition,浏览器真正加载了哪些 Artifact,以及错误究竟发生在哪条技术边界上。
微前端可以独立发布,但这不意味着它们在 Runtime 里也应该被彼此孤立地观察。
对用户来说只有一个应用。因此,Observability 必须把多个独立发布的 Frontend 重新放回同一个 Runtime Context 中。
已激活,不等于真的加载成功
Section titled “已激活,不等于真的加载成功”激活控制可以明确指定:
production-de└── Composition 184这定义了某个使用上下文应该使用哪一组产品组合。
但这仍然没有回答:某个具体浏览器里到底发生了什么?
可能某个 Remote 根本加载失败。也可能因为 Lazy Loading,整个 Session 都没有访问它,所以它从未被请求。还可能 Artifact 已经下载成功,但 Bootstrap 失败;或者 Remote 正常启动,直到后续某次 API 请求才进入错误状态。
因此必须把三个不同层次的状态分开。
目标状态、已解析 Snapshot 与浏览器实际状态
Section titled “目标状态、已解析 Snapshot 与浏览器实际状态”1. 已激活的目标状态
Section titled “1. 已激活的目标状态”激活控制例如描述:
production-de└── Composition 184问题是:
应该加载什么?
2. 已解析的 Runtime Snapshot
Section titled “2. 已解析的 Runtime Snapshot”应用启动时,Host 解析激活配置,并为当前 Session 固定选中的产品组合:
Session└── Composition 184此时的问题变成:
这个 Session 实际选择了哪个已经批准的产品版本?
这样就有了一个可复现的参照点。之后即使激活了 Composition 191,也不会自动改变这个已经固定的 Snapshot。
3. 浏览器实际观察到的状态
Section titled “3. 浏览器实际观察到的状态”而浏览器里可能出现:
Composition 184
Host 5.4.2├── Remote A 1.1.0 → 已加载├── Remote B 2.3.1 → 加载失败└── Remote C 4.7.0 → 尚未加载现在的问题是:
用户真正拿到了什么?
Composition ID 首先描述的是预期并且已经为该 Session 解析出的产品版本。Observability 还必须说明,其中哪些组成部分真正被浏览器加载、启动,或者在过程中失败。
这不会让 Composition ID 失去意义。它仍然是这次运行发生时的产品上下文。它只是不证明每个被引用的 Artifact 都成功执行了。

对用户来说只有一个应用
Section titled “对用户来说只有一个应用”产品在技术上如何拆分,对开发非常重要;对用户体验来说,首先并不重要。
如果 /members 不能使用,用户看到的就是应用坏了。错误究竟来自 Host、Remote Loader、Members Remote,还是某个 Backend Service,是诊断问题。
因此有一个重要结论:Observability 可以保留技术拆分,但这些技术信息必须能够在同一个产品上下文里被分析。
一个错误需要明确的 Owner。
而一个 Support Case 需要知道错误发生时整个产品处于什么状态。
这两件事并不矛盾。
同一个 Runtime Context 的两种视图
Section titled “同一个 Runtime Context 的两种视图”因此,Technical Observability 和 Support Diagnosis 不应该生成两套互相独立的“产品真相”。
它们只是同一个 Runtime Context 的不同投影。
Runtime Context │ ┌──────────────┴──────────────┐ ↓ ↓Technical Observability Support / Diagnostics │ │Errors, Traces, Metrics, Support ID,Performance, Load Events Version Information, User-Visible StatusTechnical Observability 可能关心:
- Composition ID;
- Host Version;
- Remote Version;
- Build ID;
- Route;
- 加载失败;
- Runtime Error;
- 加载耗时;
- API Request;
- Correlation ID;
- Trace ID。
而 Support 视图可能只需要:
产品版本2026.08.07-184
Support ID71AF-29C4内部则可以通过这个 Support ID 找到详细 Runtime Context。
Technical Observability 与 Support Diagnosis 不应该描述两个不同的应用。它们只是同一运行上下文的不同投影。
一个共同的 Runtime Context
Section titled “一个共同的 Runtime Context”一个与具体厂商无关的参考模型可以是:
Runtime Context├── Composition ID├── Release-Channel├── Host-Version├── Host-Build-ID├── Session- oder Support-ID├── Correlation- oder Trace-ID├── Auflösungszeitpunkt└── beobachtete Remote-Instanzen ├── Name ├── Version ├── Build-ID ├── Ladezustand └── Ladezeit并不是每个 Telemetry Event 都必须重复携带所有字段。重要的是,这些信息最终可以被明确关联起来。
Composition ID 描述固定的产品组合。Remote Version 方便人理解。Build ID 应该唯一指向某个具体 Artifact;Artifact Digest 还可以进一步确认真正被交付的 Bytes。Session ID、Support ID、Correlation ID 和 Trace ID 解决的是不同问题,因此不必相同。它们组合起来,让跨越技术边界的事件仍然能够被准确关联。
这不是一个全局业务 State。
共同 Runtime Context 不是 Remote 之间的业务通信。
Composition ID、Build ID 或 Correlation ID 不会破坏业务自治。它们只是说明一个事件发生时,产品处于哪个技术运行状态。

谁创建 Runtime Context?
Section titled “谁创建 Runtime Context?”一个自然的起点是 Host Bootstrap:
Host-Bootstrap├── 解析 Composition ID├── 创建 Session-/Support Context└── 初始化技术 Runtime Context当某个 Remote 成功加载后,可以补充观察结果:
Remote A 已加载└── 注册: ├── Remote A ├── Version 1.1.0 ├── Build 184 ├── 加载耗时 └── Status: started如果加载失败,Remote Code 可能永远没有机会自己报告。此时期望的 Version 和 Build ID 必须来自已经解析好的产品 Composition:
预期 Remote B├── Version 2.3.1├── Build 92└── observed status: load-failed这样就能明确区分 Composition 预期发生什么,以及浏览器真正观察到了什么。为此不需要建立一个中央业务 Registry。
技术上下文可以通过一个很小的 Observability Interface、标准化 Telemetry、共享 SDK Layer,或者现有 Browser/Tracing Mechanism 传播。
具体技术实现不是重点。重点是对这些技术身份建立稳定 Contract。
一个独立发布的 Frontend 应该知道哪些 Metadata
Section titled “一个独立发布的 Frontend 应该知道哪些 Metadata”每个独立发布的 Frontend 都应该能够技术上标识自己。
例如:
{ "name": "members", "version": "2.7.4", "buildId": "918", "commit": "8c7a5e1"}或者简化显示:
members 2.7.4+918具体格式不重要,唯一性才重要。
像 2.7.4 这样的 Semantic Version 对人很有帮助,但它未必足以唯一确定真正交付的 Bytes。同一个版本字符串不应该在 Artifact 被修改后再次无控制地复用。
因此 Build Metadata 应该在 Build 时生成,并永久属于这个 Artifact。
想要可靠复现问题,就必须知道错误发生在哪一个具体 Artifact 中。
Git Commit ID 可以作为内部诊断信息,但不意味着必须把它直接暴露在公开的诊断 UI 中。
期望版本与实际加载版本
Section titled “期望版本与实际加载版本”正常情况下,产品 Composition 和浏览器观察结果一致:
Composition 184 期望:Remote A 1.1.0
浏览器观察:Remote A 1.1.0 / Build 184真正有价值的是偏差:
Composition 184 期望:Remote B 2.3.1
浏览器:Remote B 无法加载如果架构支持受控 Fallback,也可能出现:
Composition 184 期望:Remote B 2.3.1
浏览器:Remote B 2.3.0 / Fallback那么这个偏差必须明确可见。
Observability 应同时理解 Expected State 和 Observed State。
这并不意味着自动 Fallback 总是好选择。对于强控制系统,明确失败有时反而比运行一个从未以该组合形式被批准过的 Product Composition 更安全。
哪些 Remote 真的被加载了?
Section titled “哪些 Remote 真的被加载了?”动态微前端通常不会在应用启动时一次性加载全部 Remote。
一组 Product Composition 可能包含:
Composition 184├── Remote A├── Remote B├── Remote C└── Remote D某个具体 Session 可能只访问 A 和 C:
实际加载:Remote ARemote C因此绝不能推出:
Remote B 运行正常因为 Remote B 根本没有执行。
对于加载和 Lifecycle,可以例如观察:
expectednot-loadedloadingloadedbootstrap-failedstartedunmountedRuntime Error 应该作为独立事件记录。一个成功启动的 Remote 不应该因为任何一次被捕获的 Exception 就整体变成 runtime-failed。
这些词本身不是标准。真正重要的是背后的语义。
没有加载既不是成功,也不是失败。它是一个独立、可观察的状态。
加载失败发生在 Remote Code 运行之前
Section titled “加载失败发生在 Remote Code 运行之前”一个尤其重要的场景是 Remote 根本没机会执行:
用户打开 /members ↓Host 解析 Composition 184 ↓应该加载 Remote members 2.7.4 ↓Chunk Request → 404 ↓Remote Code 从未执行Remote 自己无法可靠上报这个问题,因为它根本没有启动。
但仍然应该能够产生诊断事件:
RemoteLoadFailure├── Composition 184├── expected remote: members 2.7.4├── URL├── HTTP status: 404├── session/support ID└── timestamp这会带来一个明确的技术责任:
负责加载 Remote 的 Integration Layer 必须能够观察 Remote Loading Failure。
这不会让 Host 或 Remote Loader 成为该 Remote 的业务 Owner。它只负责观察技术集成边界。
Host└── load Remote B ├── start ├── success └── failure诊断时还应区分 Remote Load Failure、Bootstrap Failure、成功启动后的 Runtime Error,以及外部 API / Backend System 的故障。
Runtime Error 需要 Remote Ownership,也需要产品上下文
Section titled “Runtime Error 需要 Remote Ownership,也需要产品上下文”Remote 成功启动后,责任中心发生变化。
例如 Members Remote 中的 JavaScript Error 可以这样标记:
Error Event├── Composition ID: 184├── Host: 5.4.2+8231├── Remote: members├── Remote Version: 2.7.4├── Remote Build: 918├── Route: /members/42├── Correlation ID: c-8f21...└── Error: TypeError ...把错误明确归属到 Remote,有助于建立 Ownership、定向 Alert、按 Build 分析问题,并比较 Activation 前后变化。
但 Remote Context 本身还不一定够。
也许同一个 Build 只在 Composition 184 中出错:
Composition 184├── Host 5.4.2├── Members 2.7.4└── Tasks 4.2.1
错误而另一组组合完全正常:
Composition 171├── Host 5.4.2├── Members 2.7.3└── Tasks 4.2.1
无错误Remote Build 是重要诊断维度。Composition ID 则补充它真正运行时的产品上下文。
一个错误需要技术 Owner;要完成诊断,还需要知道它发生在哪个产品状态中。
关联错误,而不是把错误复制三遍
Section titled “关联错误,而不是把错误复制三遍”微前端边界会增加同一个错误被多次记录的风险。
Remote B└── 抛出错误
Host└── 在 Integration Boundary 捕获同一个错误
Global Error Handler└── 也看到了它如果没有关联机制,看起来就像三个独立错误。
更好的做法是明确记录错误源,并通过同一个事件上下文把后续观察作为 Context 或 Follow-up Event 连接起来。
例如可以使用唯一 Event/Error ID 和共同 Correlation Context。
Observability 应该丰富一个错误,而不是让每一层架构都复制一份。
同样原则也适用于 Frontend 与 Backend 之间。
Remote 可以成功加载、成功 Mount,但后续 API 失败:
members 2.7.4├── 成功加载├── 成功 Mount└── GET /members → 500诊断应该能关联:
Composition 184Remote members 2.7.4+918Frontend TraceAPI RequestBackend Trace这样,“Members 不能用”就可以被精确地解释为:
Remote 本身在运行,但它依赖的外部系统失败了。
Source Map 必须属于具体 Build
Section titled “Source Map 必须属于具体 Build”Production Code 通常会被转换、Bundle,并经常 Minify。
因此错误最初可能只长这样:
Remote A 1.1.0 / Build 184→ app.8f7c.js→ 错误位于 line 1, column 182734有了正确 Source Map,开发团队才能把 Stack Trace 重新映射到源代码。
Build 184├── app.8f7c.js└── app.8f7c.js.mapSource Map 将生成代码映射回原始 Source,因此生成 Artifact 和 Source Map 之间必须有明确、一一对应的关系。
使用另一个 Build 的 Source Map,可能把错误位置映射到完全错误的代码上,让诊断比没有 Source Map 更糟。
如果多个 Remote Version 并行运行,相应诊断 Artifact 也必须保留足够长时间。Retention 应匹配 Support、Rollback 和故障分析窗口。
Source Map 必须可用于诊断,但不代表浏览器一定能公开下载它。
一种运行方式是:
Browser└── 上报 minified Stack Trace + Build ID
Error Backend└── 使用内部保存的 Source Map SymbolicateSource Map 可能暴露源码结构,并且根据生成方式也可能包含源码内容。究竟公开交付还是内部保存,应该是明确的运维和安全决策,而不是通用规则。
Performance 来自组合后的产品
Section titled “Performance 来自组合后的产品”Performance 同样应该从用户视角看。
一次 Navigation 技术上可能分成:
Navigation→ Host 渲染→ 加载 Remote Entry→ 加载 Shared Chunks→ Remote Bootstrap→ API Response→ UI 可见因此页面慢可能有完全不同的原因。
Navigation /tasks
Host routing 20 msRemote resolution 15 msRemote load 420 msRemote bootstrap 65 msInitial API 780 msRender 40 ms共同 Runtime Context 让这些阶段可以被归入同一个 Product Composition 和 Session。
为了更好分析,可以增加技术 Measure,例如:
- Remote Load 开始;
- Remote Entry 加载完成;
- Bootstrap 开始;
- Mount 完成;
- 第一次关键 Rendering;
- Critical API 完成;
- Error 和 Timeout 时间点。
Browser 的 User Timing Mechanism 可以记录这种应用特有的 Mark 和 Measure。真正重要的并不是选哪一个 API,而是建立一致的 Measurement Model。
为什么 Web Vitals 不自动属于某一个 Remote
Section titled “为什么 Web Vitals 不自动属于某一个 Remote”Core Web Vitals 首先描述用户看到的整张页面:加载体验、视觉稳定性和交互响应性都来自组合后的产品。
因此在微前端里,不能直接说:
这个 LCP 属于 Remote A这个 INP 属于 Remote B这个 CLS 属于 Host多个 Deployable 共享 DOM、Network、Main Thread 和 Rendering Pipeline。
为了分析各自贡献,可以补充更细的 Technical Mark、Measure 或 Trace。
Local Performance Budget 仍然很有价值,例如可以阻止某个 Remote 体积无限增长。
但它不是完整产品指标。
Remote A 很快。Remote B 很快。Host 很快。组合后的产品仍然可能很慢。
原因可能是重复依赖、竞争的 Network Request、并行 Bootstrap、不必要的 Preload、Main Thread 竞争或 Layout Shift。
Local Performance Budget 很有用,但真正的用户体验来自这些部分的组合。
把 Frontend 与 Backend Event 连接起来
Section titled “把 Frontend 与 Backend Event 连接起来”很多故障不会停在 Frontend Boundary。
Browser Session / Trace ↓Remote A ↓API Request ↓Backend Service如果基础设施与安全模型允许,可以让技术 Correlation Context 穿过这些边界。
Browser Event 可以携带共同 Trace 或 Correlation Context。API Request 可以继续传递这个 Context,只要 Trust Boundary 和 Integration 允许。Backend Telemetry 随后就能和 Frontend 事件关联。
这依然不是微前端之间的业务通信。
只是让同一个技术操作跨多个系统仍然可追踪。
Trust Boundary 仍然有效。
内部 ID 不应该未经控制地发送给任意第三方 Domain 或外部 API。也不是每个 Request 都必须进入完整 Distributed Trace。
Correlation 是诊断工具,不是目的本身。

用户不需要看到内部版本矩阵
Section titled “用户不需要看到内部版本矩阵”内部完整诊断页可以展示:
应用信息
产品版本Composition 184
Host5.4.2 / Build 8231
已加载区域Members 2.7.4 / Build 918Tasks 4.2.1 / Build 1271
Release-Channelproduction-de
Support ID71AF-29C4对于普通用户,这种细节通常没有价值。
一个更小的显示就够:
Version2026.08.07-184
Support ID71AF-29C4内部 Support 可以通过 ID 解析:
Support ID 71AF-29C4└── Runtime Context ├── Composition 184 ├── Host 5.4.2+8231 ├── Members 2.7.4+918 ├── Tasks 4.2.1+1271 ├── Channel production-de ├── Session └── 相关 Telemetry用户不需要理解内部架构,Support 仍然必须能够诊断它。
这些信息究竟放在 About Page、Diagnosis Mode、Support Role 中,还是只存在内部工具里,是产品和运维决策。
架构上更重要的是:这些信息与 Error、Load Event 和 Performance Telemetry 来自同一个 Runtime Context。
Support ID 是通往技术诊断的桥
Section titled “Support ID 是通往技术诊断的桥”Support ID 应该指向技术 Runtime Context,而不是直接编码用户身份。
因此它不应该自动包含,或者可以直接推导出 E-Mail、Username、Personnel Number、Customer Number、Session Token 或 Access Token。
Support ID 应该定位一次技术运行上下文,而不是暴露用户。
Support Process 当然可能需要更多受保护的信息。但这些信息应该存在于相应安全上下文中,而不是被塞进公开可见的版本或错误信息里。
这样,Support ID 才真正成为它应有的东西:连接一次具体用户体验与对应技术运行上下文的桥。
这个 Session 到底运行了哪一个 Composition?
Section titled “这个 Session 到底运行了哪一个 Composition?”时间维度同样属于诊断的一部分。
假设 Session 启动时固定了 Composition 184:
Session A└── Composition 184在 Session 运行期间,又激活了新的产品组合:
production-de → Composition 191Session A 首先仍然保持原有 Snapshot。
新的 Session 或受控 Reload 才可能获得 Composition 191。
因此 Support Case 里问:
“现在 Production 上是哪一个版本?”
往往不够精确。
真正重要的是:
这个具体 Session 解析到了哪个 Composition ID,并且其中哪些 Build 真正执行了?
当存在并行 Release Channel、Gradual Activation 或 Context-specific Composition 时,同一时间本来就可能存在多个正确答案。
Technical Observability 不是业务耦合
Section titled “Technical Observability 不是业务耦合”共同 Observability Concept 不能成为重新拆毁业务边界的借口。
不要变成:
Host└── 理解所有 Remote 的所有业务 Event而应该类似:
共同技术 Contract├── Composition ID├── Build Metadata├── Trace-/Correlation Context├── Error Format└── Technical Lifecycle Events中心化的是技术关联信息,而不是 Remote 的业务语义。
这与其他 Shared Platform Component 的边界一样。
Observability SDK 可以是技术平台能力,但不能偷偷变成一个 Runtime Business Service,让 Remote 的业务行为离开它就不能运行。
Remote 仍然保持业务自治。
它们的技术 Runtime Event 只是被放回一个共同 Product Context 中。
什么值得 Alert?
Section titled “什么值得 Alert?”并不是每个技术 Event 都需要报警。
真正值得关注的是对用户有意义的变化,例如:
- Remote Load Failure 明显增加;
- 某次 Activation 后新出现大量 Bootstrap Failure;
- 某个 Build 的 JavaScript Error Rate 明显上升;
- API Failure Rate 异常;
- Performance 明显下降;
- Integrity 或 Manifest Error。
不存在通用阈值。安全关键流程里一个错误就可能非常严重;另一个系统里偶发网络错误可能是预期情况,并且可以自动恢复。
Alert 应该关注变化和真正的用户影响,而不是每一个孤立的技术 Exception。
从独立 Deployable 回到一个可观察的应用
Section titled “从独立 Deployable 回到一个可观察的应用”微前端把一个 Frontend 拆成可以独立负责、独立交付的单元。
这种拆分对于 Ownership、Release 和技术演进很有价值。
但到了 Runtime,这些部分必须重新作为一个产品被观察。
这不需要中央业务控制。
需要的是共同的技术身份。
Composition ID 回答:这个 Session 解析到了哪一组经过批准的 Product Composition。
Build ID 回答:具体参与的是哪些 Artifact。
Observed Remote State 回答:其中哪些真正加载并启动。
Correlation ID 和 Trace ID 把技术 Event 连接起来。
Source Map 把 Production Artifact 连接回可分析源码。
Performance Measure 把各个技术阶段连接到用户真正经历的 Journey。
Support ID 最后把这套技术视图连接到一个 Support Case,而无需要求用户理解内部架构。
因此,独立 Deployable 最终并不会产生互相独立的诊断世界,而是形成一个共同、可观察的产品状态。
所以,用户实际看到的是哪个应用?
Section titled “所以,用户实际看到的是哪个应用?”上一篇文章可以确定:某个使用上下文激活了哪个 Composition ID。但对于一个具体 Support Case 或故障,这一信息仍然不够。
Composition ID 描述的是某个 Session 解析出的 Product Composition。Observability 还必须说明其中哪些 Artifact 真正加载和启动。
每个独立发布 Frontend 都需要唯一 Build Metadata,让 Error、Source Map 和 Performance Value 能够归属到真正执行的 Artifact。
Remote Error 需要清晰 Ownership,但不能失去 Composition ID、Session 与 Correlation 构成的共同产品上下文。
Remote Load Failure 必须在技术集成边界上可观察,因为失败的 Remote Code 可能永远没有执行机会。
Source Map 必须和具体 Build 唯一绑定。如果多个版本并行运行,对应诊断 Artifact 也必须保留足够长时间。
从用户角度看,Performance 属于整个产品。Remote 级别 Measure 可以帮助找原因,但不能替代对完整 User Journey 的观察。
Host、Remote 与 Backend System 之间的技术 Correlation 不是微前端之间的业务通信。
普通用户不需要看到内部 Remote Version Matrix。一个简洁 Product Version 加一个 Support ID 就可以足够,前提是 Support 能通过它重建完整技术 Runtime Context。
并不是 Product Composition 中的每个 Remote 都会在每个 Session 里真正被加载。not-loaded、loading、started 与具体 Failure Event 必须保持可区分。
最终三种视角重新汇合:
用户└── 看到一个应用
架构└── 看到多个 Deployable
Observability└── 通过共同 Runtime Context 连接两种视角因此,“Production 上现在是什么版本?”这个问题太粗。
真正应该问的是:这个 Session 运行的是哪个 Composition ID、哪些具体 Build,以及它们实际发生了什么?
对用户来说,它只有一个应用;对诊断来说,必须能够准确重建这一刻这个应用究竟由哪些版本组成。