Compare commits

...

1 Commits

Author SHA1 Message Date
CI Bot 4eb2626d0a 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
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 不受影响。
2026-10-14 22:38:43 +08:00
+47 -21
View File
@@ -4,31 +4,57 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 标题字体(#2001 / #font-selection 修复):完全非阻塞加载中文字体
- 使用 preload + 动态 rel 切换的"无阻塞字体加载"模式:
浏览器以 preload 低优先级拉取 CSS,不会阻塞 render/DOMContentLoaded/load 任一事件
- onload 后切换 rel=stylesheet 字体才生效,首屏用系统字体兜底(Noto Sans SC → PingFang SC → Microsoft YaHei → sans-serif)
- 解决境内访问 Google Fonts 挂起时首屏白屏 + Playwright load 事件超时(E2E 也受益) -->
<link
rel="preload"
as="style"
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap"
media="print"
onload="this.media='all'; this.onload=null"
/>
<noscript
><link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap"
/></noscript>
<!-- 标题字体(#2001 / #font-selection 修复):Google Fonts 中文字体完全延迟加载
关键设计:所有 Google Fonts 子资源(CSS + 字体文件)必须在 window load 之后才发起请求。
- 原因:fonts.googleapis.com/gstatic.com 在境内访问间歇性挂起,若在关键路径内
(<link rel=stylesheet>/preload/preconnect 等任何会被计入文档子资源完成的形式),
会拖死 window.load 事件,导致 Playwright page.goto 默认 waitUntil:'load' 90s 超时,
同时真实用户首屏也可能因等字体而白屏。
- 实现:head 里完全不出现 Google 域的 <link>,首屏用系统字体兜底
(font-family fallback 链:Noto Sans SC → PingFang SC → Microsoft YaHei → sans-serif);
页面 load 后通过 requestIdleCallback 动态注入 <link rel=stylesheet>,字体异步加载生效。
- noscript 降级:JS 禁用时仍然阻塞加载(可接受,这类用户极少)。
- 参考:运维 2026-10-14 E2E 失败根因分析,#2300 preload/media=print 方案不治本,
preload 与 media=print 样式表仍计入子资源完成,会阻塞 load 事件。 -->
<title>小虾 SaaS - 自动化视频剪辑平台</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<!-- Google Fonts 延迟注入:必须在 main.tsx 之后,用独立 <script>(非 module)在页面解析
完成时注册 load 回调;window.load 触发后再 requestIdleCallback 插 <link>,
确保字体请求不进入首屏关键路径,也不阻塞 Playwright load 等待 -->
<script>
(function () {
var FONT_HREF =
"https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap";
function inject() {
if (document.getElementById("__gfonts_link")) return;
var l = document.createElement("link");
l.id = "__gfonts_link";
l.rel = "stylesheet";
l.href = FONT_HREF;
l.setAttribute("data-lazy", "1");
document.head.appendChild(l);
}
function schedule() {
if ("requestIdleCallback" in window) {
requestIdleCallback(inject, { timeout: 2000 });
} else {
setTimeout(inject, 500);
}
}
if (document.readyState === "complete") {
schedule();
} else {
window.addEventListener("load", schedule, { once: true });
}
})();
</script>
<noscript
><link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap"
/></noscript>
</body>
</html>