Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
3.1 KiB
3.1 KiB
TASK · 首页早发请求被样式表挡住(首页首开修复单)· 2026-10-01
执行方:同一 fork 子代理(产品 2026-09-30 已授权「调用 subagent 执行」)。验收:Claude。 分支
codex/home-first-load-fix-20261001,基线origin/staging(≥e600cff5)。沿用 BUG-1127,不新开号。 母单:TASK-home-first-load-20260930.mdT8。
1. 验收未通过的事实
部署 e600cff5 后,Claude 在 staging 线上用母单 §7 的方法复测(接口统一 600 ms,两次):
| 次 | /api/account 等早发请求的发出时刻 |
揭幕 |
|---|---|---|
| 1 | 2357 ms | 3844 ms |
| 2 | 3104 ms | 4240 ms |
本地 next start 下同一脚本测得早发在约 50 ms 发出。线上早发没有明显早于首屏 JS,T8 验收第 1 条不通过。
2. 根因
线上 / 的 HTML <head> 里依次是 3 个 <link rel="stylesheet">(位置 699 / 837 / 975),然后是异步 chunk,最后才是早发脚本(位置 5138,内联经典脚本)。按 HTML 规范,排在未加载完成的样式表之后的内联经典脚本要等这些样式表下载完才执行(script-blocking stylesheets)。本地样式表瞬间就到,所以测不出来;线上要多等 CSS 的下载时间。
3. 决策
- 早发逻辑、红线、消费方式都不变,只改脚本的装载方式,让它不再被样式表阻塞。
- staging 没有 CSP(
curl -sI /没有Content-Security-Policy),可以用data:URL。
4. 做法(按顺序尝试,拿到实测证据后二选一)
- 首选:
<script async src="data:text/javascript;base64,…">(内容就是现在的bootEarlyReadsScript())。带async的外部经典脚本不受样式表阻塞,data URL 不需要网络往返。 - 如果 Next / React 把它挪了位置、改了属性,或者 iOS Safari 不执行:改成放在
public/下的静态小脚本(文件名带内容哈希,长期缓存,同 T3-b 的做法),async+fetchpriority="high",并在进度记录里说明多出的那一次往返。 - 不要改
firstPaintFallbackBootScript和主题脚本,它们需要同步执行。
5. 验收
- 在测量脚本里加一个「样式表慢」开关:CDP
Fetch拦截*.css,延迟 1500 ms 再放行,其他资源正常。基线(e600cff5)和修复版各测 3 次,早发请求的发出时刻必须早于第一个样式表返回,贴对照表。 - 用无头 Chrome 的 iPhone UA 与视口,确认
window.__jyotishaBootReads已存在,并且被消费(takeBootRead之后为空)。另外,把真实 iOS Safari 这一项写进docs/testing/home-first-load-20260930.md的补充步骤(环境缺口)。 - 母单 T8 的单元测试全部保留并通过;如果脚本字符串的装载方式改变导致断言要改,写「原值 / 新值 / 原因」三栏。
tsc0 错、lint0 error、npm test失败清单与基线逐条一致、next build通过且/仍是 Static。- BUG-1127 追加「部署后复测发现被样式表阻塞 → 修复」一段;PROGRESS 母单记录追加本单一节;任务板加一行。只在分支上提交,不推送。