diff --git a/docs/technical/FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md b/docs/technical/FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md
index 4178d9d2..04ab6cbd 100644
--- a/docs/technical/FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md
+++ b/docs/technical/FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md
@@ -4,27 +4,38 @@
## 1. 背景
-网站启动后首次打开页面约需三分钟才出现可用界面。已确认 Vite dev server 本身可在数秒内 ready,因此本次不继续扩大 `api-server` 冷编译等待窗口,而是收口浏览器首屏可见链路。
+网站启动后首次打开页面约需三分钟才出现可用界面。已确认 Vite dev server 本身可在数秒内 ready,浏览器 Network 面板中主要等待项集中在 `.tsx` 模块请求,因此本次不继续扩大 `api-server` 冷编译等待窗口,而是收口浏览器首屏 `.tsx` 冷转译与默认路由依赖图。
## 2. 现象与根因
-本次排查发现两个会放大首屏等待的前端问题:
+本次排查发现三个会放大首屏等待的前端问题:
-1. `RouteImageReadyGate` 会先挂载真实业务页面但把整页 `visibility: hidden`,扫描路由 DOM 中所有 `` 和 CSS 图片,等全部图片 settled 后才显示页面。平台首页和运行时页面会渲染作品封面、角色图、图标和生成资源,任何慢图片或后端图片代理等待都会把整页可见时间拖长。
-2. Vite dev server 监听范围过宽,日志中可见 `docs/`、`scripts/`、`server-rs/` 和测试文件变更都会触发 `page reload`。后端编译、文档更新或测试文件保存会让浏览器反复全量重载,叠加首屏图片门控后表现为“首次加载一直等”。
+1. 默认路由进入 `AuthenticatedApp -> App -> RpgRuntimeShell -> PlatformEntryFlowShellImpl`,首屏虽然只显示平台首页,但入口文件静态导入了创作中心、拼图 Agent、拼图结果页、拼图运行态等非首屏阶段组件。Vite dev 首次访问时需要逐个请求并转译这些 `.tsx`,表现为浏览器长时间卡在加载 `.tsx`。
+2. `RouteImageReadyGate` 会先挂载真实业务页面但把整页 `visibility: hidden`,扫描路由 DOM 中所有 `
` 和 CSS 图片,等全部图片 settled 后才显示页面。图片不是本轮确认到的主等待项,但会放大 `.tsx` 冷转译后的可见延迟。
+3. Vite dev server 监听范围过宽,日志中可见 `docs/`、`scripts/`、`server-rs/` 和测试文件变更都会触发 `page reload`。后端编译、文档更新或测试文件保存会让浏览器反复全量重载,叠加 `.tsx` 冷转译后表现为“首次加载一直等”。
## 3. 修复口径
-### 3.1 首屏图片门控
+### 3.1 首屏 `.tsx` 冷转译
-首屏门控从“等待所有图片加载完成”改为“短暂稳态等待后放行”:
+默认首页入口先做低风险依赖图收敛:
+
+- `App`、运行时阶段路由、面板路由避免从 barrel 文件导入,改为直连具体实现文件或类型文件。
+- `PlatformEntryFlowShellImpl` 将拼图 Agent、拼图结果页、拼图详情页、拼图运行态、创作货架等非默认首屏组件改为 `lazy`。
+- 平台首页 Tab 保留已访问页面的挂载状态,但首访只挂载当前 Tab,避免隐藏的创作页提前触发创作中心等懒加载模块。
+- RPG 运行态画布和 overlay host 只在已经进入 RPG 世界后挂载,平台首页不再同步拉取运行态画布链路。
+- 平台首页资料服务直连 `rpgProfileClient`,避免经过 `services/rpg-entry/index.ts` 把同域其它 client 一并纳入冷转译链路。
+
+### 3.2 首屏图片门控
+
+图片门控从“等待所有图片加载完成”改为“短暂稳态等待后放行”:
- 页面仍先真实挂载,保留极短等待窗口,避免首帧布局剧烈闪动。
- 达到最大阻塞时间后必须显示页面,慢图片由浏览器渐进加载,不再隐藏整页。
- 页面已经显示后,不再因为新增图片或图片地址变化重新隐藏页面。
- 图片预加载继续保留,用于提前触发浏览器缓存,但不得成为首屏可见的硬阻塞。
-### 3.2 Vite 监听范围
+### 3.3 Vite 监听范围
Vite dev server 只对前端真实运行入口保持热更新敏感:
@@ -34,8 +45,9 @@ Vite dev server 只对前端真实运行入口保持热更新敏感:
## 4. 验收标准
-1. Vite ready 后,默认站点首屏不再等待所有图片完成才显示。
-2. 慢图片、失败图片或生成资源代理慢时,页面主体仍能先显示并保持可操作。
-3. 修改 `docs/`、`server-rs/`、`scripts/` 或测试文件时,不再触发前端页面 reload。
-4. `RouteImageReadyGate` 工具测试覆盖慢图片仍会放行首屏的行为。
-5. 修改中文文件后运行编码检查,确保没有破坏 UTF-8 文本。
+1. Vite ready 后,默认站点首屏不再一次性转译明显非首屏的拼图/玩法结果/运行态组件。
+2. 默认首页冷加载 `.tsx` 请求数量下降,创作、拼图、运行态等阶段在用户进入时再加载对应 chunk。
+3. 慢图片、失败图片或生成资源代理慢时,页面主体仍能先显示并保持可操作。
+4. 修改 `docs/`、`server-rs/`、`scripts/` 或测试文件时,不再触发前端页面 reload。
+5. `RouteImageReadyGate` 工具测试覆盖慢图片仍会放行首屏的行为。
+6. 修改中文文件后运行编码检查,确保没有破坏 UTF-8 文本。
diff --git a/src/App.tsx b/src/App.tsx
index dc7ec6de..89a89463 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,5 @@
-import { RpgRuntimeShell } from './components/rpg-runtime-shell';
-import { useRpgRuntimeSession } from './hooks/rpg-session';
+import { RpgRuntimeShell } from './components/rpg-runtime-shell/RpgRuntimeShell';
+import { useRpgRuntimeSession } from './hooks/rpg-session/useRpgRuntimeSession';
export default function App() {
const gameShellProps = useRpgRuntimeSession();
diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
index 1fd9925b..1553cd4d 100644
--- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
+++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
@@ -60,7 +60,7 @@ import {
createMiniGameDraftGenerationState,
type MiniGameDraftGenerationState,
} from '../../services/miniGameDraftGenerationProgress';
-import { getPlatformProfileDashboard } from '../../services/platform-entry';
+import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient';
import {
createPuzzleAgentSession,
executePuzzleAgentAction,
@@ -81,15 +81,12 @@ import { deletePuzzleWork, listPuzzleWorks } from '../../services/puzzle-works';
import { isSamePuzzlePublicWorkCode } from '../../services/publicWorkCode';
import { deleteRpgCreationAgentSession } from '../../services/rpg-creation';
import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreationPreviewAdapter';
-import { deleteRpgEntryWorldProfile } from '../../services/rpg-entry';
-import { getRpgEntryWorldGalleryDetailByCode } from '../../services/rpg-entry/rpgEntryLibraryClient';
+import {
+ deleteRpgEntryWorldProfile,
+ getRpgEntryWorldGalleryDetailByCode,
+} from '../../services/rpg-entry/rpgEntryLibraryClient';
import type { CustomWorldProfile } from '../../types';
import { useAuthUi } from '../auth/AuthUiContext';
-import { CustomWorldCreationHub } from '../custom-world-home/CustomWorldCreationHub';
-import { PuzzleAgentWorkspace } from '../puzzle-agent/PuzzleAgentWorkspace';
-import { PuzzleGalleryDetailView } from '../puzzle-gallery/PuzzleGalleryDetailView';
-import { PuzzleResultView } from '../puzzle-result/PuzzleResultView';
-import { PuzzleRuntimeShell } from '../puzzle-runtime/PuzzleRuntimeShell';
import { useRpgCreationAgentOperationPolling } from '../rpg-entry/useRpgCreationAgentOperationPolling';
import { useRpgCreationEnterWorld } from '../rpg-entry/useRpgCreationEnterWorld';
import { useRpgCreationResultAutosave } from '../rpg-entry/useRpgCreationResultAutosave';
@@ -332,6 +329,41 @@ const BigFishRuntimeShell = lazy(async () => {
};
});
+const CustomWorldCreationHub = lazy(async () => {
+ const module = await import('../custom-world-home/CustomWorldCreationHub');
+ return {
+ default: module.CustomWorldCreationHub,
+ };
+});
+
+const PuzzleAgentWorkspace = lazy(async () => {
+ const module = await import('../puzzle-agent/PuzzleAgentWorkspace');
+ return {
+ default: module.PuzzleAgentWorkspace,
+ };
+});
+
+const PuzzleResultView = lazy(async () => {
+ const module = await import('../puzzle-result/PuzzleResultView');
+ return {
+ default: module.PuzzleResultView,
+ };
+});
+
+const PuzzleGalleryDetailView = lazy(async () => {
+ const module = await import('../puzzle-gallery/PuzzleGalleryDetailView');
+ return {
+ default: module.PuzzleGalleryDetailView,
+ };
+});
+
+const PuzzleRuntimeShell = lazy(async () => {
+ const module = await import('../puzzle-runtime/PuzzleRuntimeShell');
+ return {
+ default: module.PuzzleRuntimeShell,
+ };
+});
+
function LazyPanelFallback({ label }: { label: string }) {
return (