首页首屏性能问题:启动后约十秒整页空白且无任何提示(含「首页全前端」演进方案)
概述
用户在前端调起后,首页从「全空」到「活动信息等板块正确加载」之间约有 10 秒空白,期间空白区域完全无加载提示;切换页面同样是全空、无提示,只能等待。该等待还与本地网络状态相关,开启代理/弱网时明显更久。
本 issue 记录实测数据、根因定位,以及一个按「首页全前端」方向拟定的演进方案。
可观察现象
- 应用启动后约 10 秒内,主页(以及此期间切换到的其他页面)为空白,无骨架/加载/错误提示。
- 等待结束后活动卡、方舟关卡、代理概况一次性出现。
- 延迟随网络状态(代理开启程度、弱网)明显放大。
实测数据(冷启动探针)
对 POST /api/info/get/overview 在启动后逐秒采样:
| 时间点 |
overview |
setting |
scripts |
notice |
| t≈2.7s(首次) |
17176ms |
15ms |
101ms |
7ms |
| t≈21s+ |
39ms |
12ms |
67ms |
5ms |
后端日志显示:Uvicorn running(t≈18.6s)后,到 t≈35.7s 之间 事件循环没有任何一条日志,活动服务自己发起的后台 TCP 连接也要排到 t≈35.7s 才开始。即:Uvicorn 已可接收连接,但后端事件循环被冻结了约 17 秒,这段时间内所有 API 请求(不限于 overview)都排队等待。前端此时已连上主 WebSocket、已渲染,但所有数据请求都排在冻结窗口之后——这正是「主页全空、切页也全空」的来源。
该冻结与网络无关(关闭 MCP、关闭所有远端依赖后复测仍约 17 秒),属于后端后台初始化自身的同步/CPU 占用阻塞事件循环。
另外注意:setting/scripts/notice 在同一窗口内仅几十毫秒,说明首屏慢不是这些页面慢,而是全部请求共同被冻结窗口延迟。
根因定位
「十秒空白」由两个相互独立的根因叠加:
根因 A — 后端事件循环冻结(约 17s)
main.py 后台初始化段(clean_maafw_agent_venvs → MaaFW 适配器导入 → ArknightWin32Toolkit.init → MainTimer.start)在事件循环内执行耗时初始化,逐项实测各模块导入约 1–2s,但叠加 + 进程内行为合计把事件循环卡住约 17s,期间 overview/scripts/setting 等全部接口静默排队。
根因 B — 前端引导期门禁
Home.vue:49 内容区以 layoutReady && !isBootstrapping 为条件,引导期间直接不渲染内容卡。
- 路由守卫在
isBootstrapping 为真时把一切非 /home 导航强制打回 /home。
- 因此引导期内既无内容、又不允许跳转,表现为「全空 + 切页也全空 + 无提示」。
演进方向:首页全前端
背景:首页数据当前 100% 依赖后端聚合接口 POST /api/info/get/overview(app/api/info.py:270),由 asyncio.gather 聚合 7 个第三方活动源 + 方舟关卡 + 代理历史后一次性返回;前端(useHomeOverview.ts:66) 只是渲染壳 + 轮询兜底。
拟定方向为「首页全前端」:让活动卡在浏览器端直接请求各第三方源,逐卡独立超时/缓存/失败,从而不再受后端事件循环约束。
可行性:CORS 已实测全部放行
以 Origin: http://localhost:5173 + cors 模式探测各源响应头:
| 卡片 |
来源 host |
Access-Control-Allow-Origin |
| 星穹 / 原神 / 绝区零 / 鸣潮 / 异环(5 卡) |
starrailassistant.top |
* |
| 重返未来 |
api.1999.fan |
* |
| 终末地 |
data.akedata.wiki |
* |
* 对开发环境 http://localhost 与生产版 file://(null origin,无凭证)均成立,浏览器直连技术上可行。
关键:全前端并非字面全前端
- 方舟关卡:依赖后端关卡缓存 + 硬编码资源表(
RESOURCE_STAGE_INFO / RESOURCE_STAGE_DATE_TEXT 等)。
- 代理概况:读取本机
history/ 目录(search_history / merge_statistic_info),属本地数据。
这两张卡第三方源给不了,必须留后端。因此目标形态是「7 张活动卡走前端 + 方舟/代理 2 张卡留后端」,后端聚合接口相应瘦身。
各卡成本差异
| 卡片 |
成本 |
| SRA 5 卡(sr/ys/zzz/ww/nte) |
最低:同 host 同格式,一个解析器复用 |
| 重返未来 |
中:单 JSON(api.1999.fan/.../cn.json) |
| 终末地 |
高:需拉 manifest.json + 6 张 GameTable JSON,再做 i18n 文本解析 + 时区换算合并 |
分阶段演进方案
- Phase 0 — 前端引导期解卡(最便宜,收益直观):去掉
!isBootstrapping 对内容卡的硬性隐藏,改为卡片各自显示骨架/加载态;引导期放行导航,各页自处理 loading。消除「全空且无提示 + 切页全空」。
- Phase 1 — 后端事件循环解冻(全前端与否都必须做):将
main.py 后台初始化中冻结事件循环的段用 asyncio.to_thread 挪出主循环,或推迟到首屏之后。令 overview/scripts/setting 等从 17s 降至毫秒级。
- Phase 2 — SRA 5 卡转前端:新建前端 composable 直连
starrailassistant.top/api/v1/activity/{game}.json,逐卡超时/缓存/退避;类型复用 types/home.ts 的 SraActivityOverview。
- Phase 3 — 重返未来转前端:复用 Phase 2 管线,仅 URL/字段不同。
- Phase 4 — 终末地:决策点。建议先留后端(Phase 1 解冻后测实测再定);完整 TS 移植(7 文件管线 + i18n + 时区)成本高,仅当解冻后仍显慢才投入。
- Phase 5 — 后端瘦身:
/get/overview 移除已迁前端的服务,只回 Stage + Proxy(或拆 /overview/local);删除后端无调用方的活动服务/缓存,避免双份逻辑漂移。
结构性风险
- 第三方源改格式/挂掉:前端逐卡独立超时 + 失败态;保留后端缓存作兜底(Phase 1 后成本低)。
- 跨端逻辑双份/漂移:Phase 5 删除后端旧逻辑,只留单一归属方。
- 终末地复刻成本:先 A 后 B,以实测决定,不预先投入。
file:// + * CORS 边角:已实测 null 源可用,生产无凭证请求确认即可。
- 引导期/路由门禁未同步改:Phase 0 独立前置。
优先级建议
Phase 0 → Phase 1 → 评估 → Phase 2 →(Phase 3/4 按实测)。Phase 0+1 即可将「全空十秒」降为亚秒级且带骨架,且是「全前端」方向的共同前提(方舟/代理依赖 Phase 1);Phase 2 为拟定方向的核心落地,以实测决定 Phase 4 的 A/B。
首页首屏性能问题:启动后约十秒整页空白且无任何提示(含「首页全前端」演进方案)
概述
用户在前端调起后,首页从「全空」到「活动信息等板块正确加载」之间约有 10 秒空白,期间空白区域完全无加载提示;切换页面同样是全空、无提示,只能等待。该等待还与本地网络状态相关,开启代理/弱网时明显更久。
本 issue 记录实测数据、根因定位,以及一个按「首页全前端」方向拟定的演进方案。
可观察现象
实测数据(冷启动探针)
对
POST /api/info/get/overview在启动后逐秒采样:后端日志显示:
Uvicorn running(t≈18.6s)后,到 t≈35.7s 之间 事件循环没有任何一条日志,活动服务自己发起的后台 TCP 连接也要排到 t≈35.7s 才开始。即:Uvicorn 已可接收连接,但后端事件循环被冻结了约 17 秒,这段时间内所有 API 请求(不限于 overview)都排队等待。前端此时已连上主 WebSocket、已渲染,但所有数据请求都排在冻结窗口之后——这正是「主页全空、切页也全空」的来源。该冻结与网络无关(关闭 MCP、关闭所有远端依赖后复测仍约 17 秒),属于后端后台初始化自身的同步/CPU 占用阻塞事件循环。
另外注意:
setting/scripts/notice在同一窗口内仅几十毫秒,说明首屏慢不是这些页面慢,而是全部请求共同被冻结窗口延迟。根因定位
「十秒空白」由两个相互独立的根因叠加:
根因 A — 后端事件循环冻结(约 17s)
main.py后台初始化段(clean_maafw_agent_venvs→ MaaFW 适配器导入 →ArknightWin32Toolkit.init→MainTimer.start)在事件循环内执行耗时初始化,逐项实测各模块导入约 1–2s,但叠加 + 进程内行为合计把事件循环卡住约 17s,期间overview/scripts/setting等全部接口静默排队。根因 B — 前端引导期门禁
Home.vue:49内容区以layoutReady && !isBootstrapping为条件,引导期间直接不渲染内容卡。isBootstrapping为真时把一切非/home导航强制打回/home。演进方向:首页全前端
背景:首页数据当前 100% 依赖后端聚合接口
POST /api/info/get/overview(app/api/info.py:270),由asyncio.gather聚合 7 个第三方活动源 + 方舟关卡 + 代理历史后一次性返回;前端(useHomeOverview.ts:66) 只是渲染壳 + 轮询兜底。拟定方向为「首页全前端」:让活动卡在浏览器端直接请求各第三方源,逐卡独立超时/缓存/失败,从而不再受后端事件循环约束。
可行性:CORS 已实测全部放行
以
Origin: http://localhost:5173+ cors 模式探测各源响应头:Access-Control-Allow-Originstarrailassistant.top*api.1999.fan*data.akedata.wiki**对开发环境http://localhost与生产版file://(null origin,无凭证)均成立,浏览器直连技术上可行。关键:全前端并非字面全前端
RESOURCE_STAGE_INFO/RESOURCE_STAGE_DATE_TEXT等)。history/目录(search_history/merge_statistic_info),属本地数据。这两张卡第三方源给不了,必须留后端。因此目标形态是「7 张活动卡走前端 + 方舟/代理 2 张卡留后端」,后端聚合接口相应瘦身。
各卡成本差异
api.1999.fan/.../cn.json)manifest.json+ 6 张 GameTable JSON,再做 i18n 文本解析 + 时区换算合并分阶段演进方案
!isBootstrapping对内容卡的硬性隐藏,改为卡片各自显示骨架/加载态;引导期放行导航,各页自处理 loading。消除「全空且无提示 + 切页全空」。main.py后台初始化中冻结事件循环的段用asyncio.to_thread挪出主循环,或推迟到首屏之后。令overview/scripts/setting等从 17s 降至毫秒级。starrailassistant.top/api/v1/activity/{game}.json,逐卡超时/缓存/退避;类型复用types/home.ts的SraActivityOverview。/get/overview移除已迁前端的服务,只回Stage + Proxy(或拆/overview/local);删除后端无调用方的活动服务/缓存,避免双份逻辑漂移。结构性风险
file://+*CORS 边角:已实测 null 源可用,生产无凭证请求确认即可。优先级建议
Phase 0 → Phase 1 → 评估 → Phase 2 →(Phase 3/4 按实测)。Phase 0+1 即可将「全空十秒」降为亚秒级且带骨架,且是「全前端」方向的共同前提(方舟/代理依赖 Phase 1);Phase 2 为拟定方向的核心落地,以实测决定 Phase 4 的 A/B。