fix(web): Google Fonts 延迟到 window.load 后注入,根治E2E load事件阻塞 #2301

Merged
xiaoxia merged 1 commits from fix/web-fonts-load-after-window-load into develop 2026-10-14 22:56:16 +08:00
Owner

问题

#2300 的 preload + media=print 方案不能根治 E2E 超时。运维实锤:

  • preload 和 media=print 样式表都属于文档子资源
  • window 的 load 事件会等所有在途子资源请求完成
  • Google Fonts 从境内 runner 访问间歇性挂起 → 请求永不完成 → load 永不触发 → Playwright page.goto(默认等 load)90s 超时
  • preload/media=print 只解决 FOIT/白屏(渲染阻塞),不解决 load 事件阻塞

方案(选 B)

按运维建议的三条路:A 自托管、B JS 在 load 后动态插 link、C Playwright route.abort。

选 B(对产品无副作用、用户也受益、比自托管快):

  • head 里彻底移除所有 Google 域的 link/preconnect/preload,初始 HTML 不含任何 Google Fonts 子资源
  • 在 </body> 前用内联普通 <script>(非 module,不受 main.tsx 异步加载影响)注册 window.load 回调
  • load 触发后用 requestIdleCallback(fallback setTimeout 500ms,timeout 兜底 2s)动态 createElement(link) append 到 head
  • <noscript> 降级 JS 禁用场景

效果

  • 初始 HTML 的子资源全是同源(/assets JS/CSS/img + /vite.svg)→ load 毫秒级触发
  • E2E page.goto 默认 waitUntil:load 不会再被 Google Fonts 拖死
  • 真实用户首屏用系统字体兜底(PingFang SC → Microsoft YaHei → sans-serif),不白屏,字体加载完自动应用
  • requestIdleCallback 确保字体在浏览器空闲时才加载,不抢占首屏交互资源
  • 幂等:__gfonts_link id 防重复注入

影响范围

仅改 apps/web/index.html 单文件。后端/worker 无改动。staging 部署前端即可验证。

为什么不选 A/C

  • A(自托管):中文字体体积 5-15MB,打包爆增,留作长期优化
  • C(Playwright route.abort):只救 CI,真实用户在境内仍可能白屏,不治本

cc @运维 合入后麻烦跑一次 Staging E2E 验证回绿。

## 问题 #2300 的 preload + media=print 方案不能根治 E2E 超时。运维实锤: - `preload` 和 `media=print` 样式表都属于文档子资源 - window 的 `load` 事件会等所有在途子资源请求完成 - Google Fonts 从境内 runner 访问间歇性挂起 → 请求永不完成 → `load` 永不触发 → Playwright `page.goto`(默认等 load)90s 超时 - preload/media=print 只解决 FOIT/白屏(渲染阻塞),不解决 load 事件阻塞 ## 方案(选 B) 按运维建议的三条路:A 自托管、B JS 在 load 后动态插 link、C Playwright route.abort。 选 **B**(对产品无副作用、用户也受益、比自托管快): - **head 里彻底移除所有 Google 域的 link/preconnect/preload**,初始 HTML 不含任何 Google Fonts 子资源 - 在 `</body>` 前用内联普通 `<script>`(非 module,不受 main.tsx 异步加载影响)注册 `window.load` 回调 - `load` 触发后用 `requestIdleCallback`(fallback `setTimeout 500ms`,timeout 兜底 2s)动态 `createElement(link)` append 到 head - `<noscript>` 降级 JS 禁用场景 ## 效果 - 初始 HTML 的子资源全是同源(/assets JS/CSS/img + /vite.svg)→ `load` 毫秒级触发 - E2E `page.goto` 默认 `waitUntil:load` 不会再被 Google Fonts 拖死 - 真实用户首屏用系统字体兜底(PingFang SC → Microsoft YaHei → sans-serif),不白屏,字体加载完自动应用 - `requestIdleCallback` 确保字体在浏览器空闲时才加载,不抢占首屏交互资源 - 幂等:`__gfonts_link` id 防重复注入 ## 影响范围 仅改 `apps/web/index.html` 单文件。后端/worker 无改动。staging 部署前端即可验证。 ## 为什么不选 A/C - A(自托管):中文字体体积 5-15MB,打包爆增,留作长期优化 - C(Playwright route.abort):只救 CI,真实用户在境内仍可能白屏,不治本 cc @运维 合入后麻烦跑一次 Staging E2E 验证回绿。
auto-approve-bot approved these changes 2026-10-14 22:21:18 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot approved these changes 2026-10-14 22:21:18 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。

🚀 预览环境已部署

项目 详情
PR号 #2301
预览链接 https://pr-2301.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #2301 | | 预览链接 | [https://pr-2301.preview.xiaoxiajianji.com](https://pr-2301.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
Collaborator

Auto merge skipped after multiple 405 errors: PR may have conflicts or unresolved checks. Please review manually. This is not a CI failure.

Auto merge skipped after multiple 405 errors: PR may have conflicts or unresolved checks. Please review manually. This is not a CI failure.
xiaoxia added 1 commit 2026-10-14 22:39:03 +08:00
fix(web): Google Fonts 延迟到 window.load 后注入,彻底排除E2E load事件阻塞
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m43s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m4s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m5s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m10s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 5m19s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m40s
AI Code Review / AI Code Review (pull_request) Successful in 8m26s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m37s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 13m52s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 37s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m17s
4eb2626d0a
window.load 会等待所有在途子资源完成,Google Fonts 挂起时请求永不完成→load 永不触发。

改走 B 方案:head 里彻底不出现任何 Google 域的 <link>/<link rel=preconnect>,
内联普通 <script>(非 module,不受 main.tsx 异步加载影响)在 window.load 之后用
requestIdleCallback 动态 createElement('link') 注入字体样式表:

- 初始 HTML 不含 Google 子资源,同源 CSS/JS/img 加载完即触发 load(毫秒级)
- E2E Playwright page.goto 默认 waitUntil:'load' 不再被拖死
- 真实用户首屏用系统字体兜底(PingFang/Microsoft YaHei/sans-serif),不白屏
- requestIdleCallback 无浏览器支持时 fallback setTimeout 500ms
- timeout:2000 兜底,即使浏览器一直 busy 也最多等 2s 就注入
- <noscript> 降级 JS 禁用场景(极少用户)
- 字体加载完后浏览器自动应用,已有内容不重排(中文字体度量接近)

仅改 apps/web/index.html 单文件,前端重部署即可,后端/worker 不受影响。
xiaoxia force-pushed fix/web-fonts-load-after-window-load from cfe567b062 to 4eb2626d0a 2026-10-14 22:39:03 +08:00 Compare
xiaoxia merged commit 50e833eeeb into develop 2026-10-14 22:56:16 +08:00

🗑️ 预览环境已清理

PR #2301 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #2301 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.