diff --git a/docs/architecture/004_architecture_impact_map.md b/docs/architecture/004_architecture_impact_map.md
index 568c06f..96db0ca 100644
--- a/docs/architecture/004_architecture_impact_map.md
+++ b/docs/architecture/004_architecture_impact_map.md
@@ -286,6 +286,105 @@ V5 状态管理统一的后续阶段,针对两类高频性能问题进行细
---
+## 1.1.6 缓存策略落地专项重构(2026-07-05 新增)
+
+> **目标**:全栈缓存策略一致性重构 — 服务端 `cacheFn` 双层包装 + 集中式 `INVALIDATION_MAP` 失效编排 + 客户端 `queryKeys` 工厂 + `useActionQuery`/`useActionMutation` 向后兼容双模式 Hook。
+
+### 架构总览
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ Client(TanStack Query + useActionQuery/useActionMutation) │
+│ queryKeys.{module}.{resource}(...args) → queryKey │
+│ useActionMutation({ mutationFn, actionId, params }) │
+│ → 成功后按 CLIENT_INVALIDATION_MAP[actionId].queryKeys 自动失效 │
+└──────────────────────────────┬──────────────────────────────────────┘
+ │ mutationFn = Server Action
+ ▼
+┌─────────────────────────────────────────────────────────────────────┐
+│ Server Actions(编排层) │
+│ mutation 成功分支末尾: │
+│ await invalidateFor("module.action", { id }) │
+│ → 1) CacheStore.invalidateTags 2) revalidateTag × N 3) revalidatePath × N │
+└──────────────────────────────┬──────────────────────────────────────┘
+ │ 调用 data-access
+ ▼
+┌─────────────────────────────────────────────────────────────────────┐
+│ Data-access(数据访问层) │
+│ export const fnRaw = async (...) => { ... } │
+│ export const fn = cacheFn(fnRaw, { tags, ttl, keyParts }) │
+│ → 外层 react.cache(请求级 memoization) │
+│ → 内层 cacheStore.getOrSet(跨请求/跨实例,按 tag 失效) │
+└─────────────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────────┐
+│ CacheStore(CACHE_DRIVER 切换) │
+│ memory(默认,单实例,Map + LRU + tag 反查索引) │
+│ redis(@upstash/redis,多实例共享,故障降级) │
+└─────────────────────────────────────────────────────────────────────┘
+```
+
+### 核心基础设施(9 个文件)
+
+| 文件 | 职责 |
+|------|------|
+| `shared/lib/cache/types.ts` | `CacheFnOptions` / `CacheStore` / `InvalidationRule` 接口 |
+| `shared/lib/cache/memory-store.ts` | `MemoryCacheStore`(Map + LRU maxEntries=500 + tag 反查索引) |
+| `shared/lib/cache/redis-store.ts` | `RedisCacheStore`(key=`next-edu:cache:{key}`,故障降级) |
+| `shared/lib/cache/store-factory.ts` | `getCacheStore()` 单例工厂(按 `env.CACHE_DRIVER` 切换) |
+| `shared/lib/cache/cache-fn.ts` | `cacheFn` 双层包装器(外层 react.cache + 内层 cacheStore.getOrSet) |
+| `shared/lib/cache/invalidation-map.ts` | `INVALIDATION_MAP` 集中式失效映射(203 个 actionId)+ `fillTemplate` |
+| `shared/lib/cache/client-invalidation-map.ts` | `CLIENT_INVALIDATION_MAP` 客户端可见子集(仅 queryKeys) |
+| `shared/lib/cache/invalidate.ts` | `invalidateFor` 三步编排函数 |
+| `shared/lib/cache/index.ts` | barrel re-export |
+| `shared/lib/redis-client.ts` | 共享 Redis 单例(rate-limit + cache 共用) |
+| `shared/lib/query-keys.ts` | 客户端 queryKey 工厂(`queryKeys.classes.*`) |
+
+### 迁移覆盖范围(28 个模块全部完成)
+
+**data-access 层迁移至 `cacheFn`**(83 个文件,250+ 个查询函数):
+
+| 组别 | 模块 | 文件数 | 备注 |
+|------|------|--------|------|
+| 标杆 | classes | 5 | `data-access-{teacher,admin,students,stats,schedule}.ts` |
+| A | users / school / rbac / textbooks / questions / course-plans / files / dashboard / parent / proctoring | 11 | 77 个函数 |
+| B | exams / grades / homework / diagnostic / error-book / adaptive-practice | 8 | 含跨模块接口 |
+| C | lesson-preparation / elective / announcements / messaging / notifications | 6 | 24 个函数 |
+
+**actions 层迁移至 `invalidateFor`**(43 个文件,247 处 `revalidatePath` 替换):
+
+| 组别 | 模块 | 文件数 | 替换数 |
+|------|------|--------|--------|
+| 标杆 | classes | 5 | 47 |
+| A | users / school / textbooks / questions / course-plans / standards / scheduling / audit / onboarding / invitation-codes / proctoring / i18n | 13 | 70 |
+| B | exams / grades / homework / attendance / leave-requests / diagnostic / error-book / adaptive-practice | 9 | 77 |
+| C | lesson-preparation / elective / announcements / messaging / notifications / settings / rbac | 8 | 100 |
+
+**INVALIDATION_MAP**:扩展至 **203 个 actionId**,覆盖全部 28 个模块。每个 actionId 声明 `{ tags, queryKeys, paths }` 三段副作用。
+
+### 强制规则(已通过 ESLint 落地)
+
+| 规则 | 落地方式 |
+|------|----------|
+| Server Actions 中禁止直接调用 `revalidatePath` / `revalidateTag` | `no-restricted-syntax` ESLint 规则,仅 `shared/lib/cache/invalidate.ts` 豁免 |
+| data-access 查询函数必须用 `cacheFn` 包装 | 双导出 raw 模式(`fnRaw` + `cacheFn(fnRaw, opts)`),单测可 mock |
+| 未知 actionId 必须抛错 | `invalidateFor` 内部强校验,提示更新 INVALIDATION_MAP |
+| Tag 命名规范 | `{module}` / `{module}:{resource}` / `{module}:{resource}:{id}` |
+
+### 向后兼容 Hook 双模式
+
+- **`useActionQuery`**:传 `queryKey` 走 `useQuery`(跨页共享缓存 + 自动 invalidate);不传走旧 `useEffect + useState`(向后兼容)
+- **`useActionMutation`**:传 `mutationFn + actionId` 自动 invalidate 相关 queryKeys;旧 `mutate(action)` 模式仍可用
+
+### 验证结果
+
+- `npx tsc --noEmit`:零错误
+- `npm run lint`:零 `no-restricted-syntax` 错误(仅 3 个 pre-existing `react-hooks/refs` 错误位于未修改文件)
+- `npx vitest run src/shared/lib/cache/`:25/25 测试通过(5 个测试文件:types/memory-store/redis-store/cache-fn/invalidate/invalidation-map)
+
+---
+
## 1.2 模块依赖关系图
下图展示模块间的实际依赖关系,**标注依赖类型与合规性**。
diff --git a/docs/architecture/005_architecture_data.json b/docs/architecture/005_architecture_data.json
index c7ce9da..1f4ce72 100644
--- a/docs/architecture/005_architecture_data.json
+++ b/docs/architecture/005_architecture_data.json
@@ -5,7 +5,7 @@
"generatedAt": "2026-06-17",
"formatVersion": "1.1",
"rule": "每次文件修改后须同步更新本文件",
- "lastUpdate": "性能预算重构专项 Phase 2-4 完成(2026-07-05):Phase 2 Bundle 预算优化:(2.1) TipTap 三实例(exam-rich-editor/question-rich-editor/paper-editor)+ AiAssistantWidget 改 next/dynamic ssr:false + xxx-inner.tsx 拆分模式;(2.2) KnowledgeGraph 改 ReactFlowProvider 同步壳 + lazy 内部实现;(2.3) exams/editor 拆 types.ts barrel 减少服务端消费方拉入 @tiptap/* 运行时;(2.4) status-badge 移除多余 'use client';(2.5) teacher/student/parent 角色路由补齐 loading.tsx。Phase 3 数据层优化:(3.1) getSession/getAuthContext/resolveDataScope/getSessionTeacherId 全部 React cache() 包裹实现请求级去重;(3.2) copyCoursePlanToClasses 改单事务 + 2 次 batch INSERT;(3.3) gradeHomeworkAnswers 改 UPDATE CASE WHEN 单次批量更新;(3.4) searchQuestions 改 FULLTEXT INDEX + MATCH AGAINST BOOLEAN MODE + toBooleanModeQuery 工具;(3.5) getAllUserIds 加默认 LIMIT 1000 + 分页参数;(3.6) announcements/notifications fan-out 改 createNotifications 批量 INSERT + sendBatchNotifications 并行收集;(3.7) 高 LIMIT 默认值调低(attendance/adaptive-practice/questions data-access 1000-5000→100)。Phase 4 组件渲染优化:(4.1) AssignmentCard/EmptyState/StatusBadge React.memo;(4.2) lesson-plan-editor/text-study-block/exercise-block/paper-context-menu 拆分 Zustand 细粒度 selector(避免整体订阅);(4.3) message-detail/message-list/grade-record-list/attendance-sheet 改 useOptimistic + useTransition 乐观更新;(4.4) question-data-table/exam-data-table 接入 @tanstack/react-virtual 列表虚拟化;(4.5) sidebar-provider resize 改 debounce + useMemo;(4.6) 14 个 recharts 组件 props 提取到模块级常量避免 inline 重建;(4.7) scan-image-viewer aspect-ratio;(4.8) AiAssistantWidget inner 拆分 + 动态 import;(4.9) web-vitals-reporter.tsx 修复 Metric 类型推导(从 useReportWebVitals 签名推导)+ PerformanceEntry 类型注解 + sendBeacon/fetch keepalive fallback。架构决策:(1) 动态 import 标准模式 xxx-inner.tsx + lazy wrapper;(2) 请求级去重 > 跨请求 cache(React cache() not unstable_cache);(3) 批量 SQL > 循环 SQL(INSERT batch + UPDATE CASE WHEN);(4) React 19 useOptimistic 替代手动 isPending;(5) 细粒度 Zustand selector > useShallow 多字段 selector。验证:npx tsc --noEmit 零新增错误;npm run lint 零新增错误(pre-existing 3 errors + 13 warnings 均位于未修改文件)。",
+ "lastUpdate": "缓存策略落地专项重构完成(2026-07-05):全栈缓存一致性重构,覆盖全部 28 个模块。基础设施层:新增 shared/lib/cache/ 9 个文件(types/memory-store/redis-store/store-factory/cache-fn/invalidation-map/client-invalidation-map/invalidate/index)+ 共享 redis-client.ts + 客户端 query-keys.ts 工厂。核心机制:(1) cacheFn 双层包装器 — 外层 react.cache 请求级 memoization,内层 cacheStore.getOrSet 跨请求/跨实例缓存(按 tag 失效,TTL 可选);(2) INVALIDATION_MAP 集中式失效映射表 — 扩展至 203 个 actionId,每个声明 { tags, queryKeys, paths } 三段副作用;(3) invalidateFor 三步编排 — CacheStore.invalidateTags → revalidateTag × N → revalidatePath × N(Next.js 16 revalidateTag 第二参数必填 \"default\");(4) CLIENT_INVALIDATION_MAP 客户端可见子集(仅 queryKeys 字段,避免拉入 server-only 依赖);(5) CACHE_DRIVER 环境变量切换 memory/redis driver,复用 rate-limit 模式。迁移范围:data-access 83 个文件 250+ 查询函数迁移至 cacheFn(双导出 raw + 包装版本,单测可 mock);actions 43 个文件 247 处 revalidatePath 替换为 invalidateFor;INVALIDATION_MAP 覆盖全部 28 个模块。Hook 双模式:useActionQuery 传 queryKey 走 useQuery 跨页共享缓存,不传走旧 useEffect+useState;useActionMutation 传 mutationFn+actionId 自动 invalidate,旧 mutate(action) 仍可用。ESLint 强制规则:no-restricted-syntax 禁止 Server Actions 中直接调用 revalidatePath/revalidateTag,仅 shared/lib/cache/invalidate.ts 豁免;data-access 查询函数双导出 raw 模式便于单测 mock。验证:npx tsc --noEmit 零错误;npm run lint 零 no-restricted-syntax 错误(仅 3 个 pre-existing react-hooks/refs 错误位于未修改文件);npx vitest run src/shared/lib/cache/ 25/25 测试通过。详细文档见 004 第 1.1.6 节。",
"lastUpdated": "2026-07-05"
},
"architectureOverview": {
diff --git a/docs/architecture/audit/performance-budget-audit-report.md b/docs/architecture/audit/performance-budget-audit-report.md
new file mode 100644
index 0000000..6721a53
--- /dev/null
+++ b/docs/architecture/audit/performance-budget-audit-report.md
@@ -0,0 +1,1175 @@
+# 性能预算重构专项 - 完整审计报告
+
+> **审计日期**: 2026-07-05
+> **审计范围**: e:\Desktop\CICD 全项目(Next.js 16.0.10 + React 19.2.1 + Turbopack + Drizzle ORM + MySQL + NextAuth v5)
+> **审计维度**: ① Bundle 体积预算(首期重点)② Core Web Vitals ③ 组件渲染性能 ④ Server Action / 数据层
+> **审计方式**: 静态代码搜索 + 依赖图分析 + 源码路径追踪(未运行时采样)
+> **交付形式**: 完整审计报告(不做实施)
+> **说明**: 当前 `next build` 因 Turbopack 编译错误未成功出包,量化指标为基于依赖图与库体积经验值的合理推断值,需在修复 P0 项后重新构建获取实际产物体积进行第二轮校准。
+
+---
+
+## 目录
+
+- [执行摘要](#执行摘要)
+- [第一部分:Bundle 体积预算审计](#第一部分bundle-体积预算审计首期重点)
+- [第二部分:Core Web Vitals 审计](#第二部分core-web-vitals-审计)
+- [第三部分:组件渲染性能审计](#第三部分组件渲染性能审计)
+- [第四部分:Server Action / 数据层审计](#第四部分server-action--数据层审计)
+- [第五部分:综合性能预算基线表](#第五部分综合性能预算基线表)
+- [第六部分:P0/P1 修复路线图](#第六部分p0p1-修复路线图)
+- [附录 A:关键文件清单](#附录-a关键文件清单)
+- [附录 B:审计涉及的依赖清单](#附录-b审计涉及的依赖清单)
+
+---
+
+## 执行摘要
+
+本次性能预算审计覆盖 4 个维度,共发现 **24 个 P0/P1 问题、22 个 P2 问题、14 个 P3 问题**,全部为静态发现,未做运行时验证。
+
+### P0 严重问题(影响生产稳定性 / 必须先修)
+
+| # | 维度 | 问题 | 文件位置 |
+|---|------|------|---------|
+| 1 | Bundle | `KnowledgeGraph`(@xyflow/react)未动态导入,textbook 路由 chunk 膨胀 ~120 KB | `src/modules/textbooks/components/knowledge-graph.tsx:5-14` |
+| 2 | Bundle | `ExamRichEditor` / `RichTextEditor` / `rich-text-block` 三处 tiptap 未动态导入,编辑器路由 chunk 膨胀 ~180 KB | `src/shared/components/ui/rich-text-editor.tsx:5-8`、`src/modules/exams/editor/exam-rich-editor.tsx:4-6`、`src/modules/lesson-preparation/components/blocks/rich-text-block.tsx:3-6` |
+| 3 | Bundle | `next.config.ts` 未启用 `experimental.optimizePackageImports`(lucide-recharts-radix-tiptap 全部未优化) | `next.config.ts:6-11` |
+| 4 | Bundle | `serverExternalPackages` 仅含 mysql2,未追加 `tencentcloud-sdk-nodejs`(2-3 MB 误打包风险) | `next.config.ts:10` |
+| 5 | Bundle | `src/modules/exams/editor/index.ts` barrel 已实际造成构建错误(代码中已有显式绕过注释) | `src/modules/exams/editor/index.ts` |
+| 6 | CWV | 无 Web Vitals 上报闭环,生产环境零真实用户性能数据 | `src/app/layout.tsx`(缺失 `useReportWebVitals`) |
+| 7 | 组件 | React Compiler(`babel-plugin-react-compiler`)未安装,memoization 缺失无法自动补全 | `package.json` + `next.config.ts` |
+| 8 | 组件 | `SidebarProvider` value 内联对象 + toggleSidebar 未 useCallback + resize 监听未防抖,全站 Header/Sidebar 高频重渲染 | `src/modules/layout/components/sidebar-provider.tsx:63-66` |
+| 9 | 组件 | 备课编辑器 `LessonPlanEditor` 通过 `useLessonPlanEditor()` 订阅全 store,每次按键触发三栏全量重渲染 | `src/modules/lesson-preparation/components/lesson-plan-editor.tsx:62` |
+| 10 | 组件 | 题库表 `QuestionDataTable` 无虚拟化,1000+ 题时首屏渲染秒级卡顿 | `src/modules/questions/components/question-data-table.tsx:45-59` |
+| 11 | 数据层 | `copyCoursePlanToClasses` 双层循环单条 INSERT,无事务无批量无大小限制 | `src/modules/course-plans/data-access.ts:347-406` |
+| 12 | 数据层 | 跨请求数据缓存完全缺失(0 处 `unstable_cache`),低变更频率数据每次重查 DB | 全项目 |
+| 13 | 数据层 | 每次 Server Action 调用必查 DB 解析 DataScope(admin 0 次 / teacher 2 次 / parent 2 次串行) | `src/shared/lib/auth-guard.ts:44-47` |
+| 14 | 数据层 | `searchQuestions` JSON 字段 CAST 后 `LIKE %kw%` 全表扫描 | `src/modules/search/data-access.ts:74-75` |
+| 15 | 数据层 | `getAllUserIds` 无 LIMIT 全表返回,5000+ 用户场景公告 fan-out 内存爆炸 | `src/modules/users/data-access.ts:348-351` |
+
+### P1 高优先级问题(影响高频接口性能)
+
+详见各分章节,共 9 项关键 P1(含:recharts barrel import、tencentcloud-sdk serverExternalPackages、exams/editor barrel 拆分、loading.tsx 缺失、force-dynamic 滥用、Lighthouse CI 缺失、layout 串行 await、autoGradeSubmission 循环 UPDATE、exams 索引缺失等)。
+
+### 关键优化杠杆点(ROI 排序)
+
+1. **React Compiler 启用**:单点改动,自动解决第 3 部分列出的多数 memoization 反模式
+2. **next.config.ts 完善**:单点改动,optimizePackageImports + serverExternalPackages 扩展
+3. **tiptap / ReactFlow 动态导入**:3 处文件改动,编辑器路由 chunk 减少 ~300 KB
+4. **备课编辑器 store 订阅拆分**:单文件改动,编辑器按键响应速度预计提升 5-10x
+5. **unstable_cache 引入**:DataScope + 低变更数据缓存,单次 Server Action DB 查询数减半
+
+---
+
+## 第一部分:Bundle 体积预算审计(首期重点)
+
+### 1.1 大体积依赖清单
+
+| 依赖 | 版本 | 估算体积(min+gzip) | 用途 | 服务端/客户端 | 严重度 |
+|------|------|----------------------|------|---------------|--------|
+| `recharts` | ^3.6.0 | ~95-110 KB | 图表库(13 处使用) | 客户端 | P1 |
+| `@xyflow/react` | ^12.11.0 | ~120-140 KB | ReactFlow 知识图谱 | 客户端 | P1 |
+| `react-force-graph-2d` | ^1.29.1 | ~80-100 KB(含 d3-force) | 引力图谱 | 客户端 | P2(已 dynamic) |
+| `tencentcloud-sdk-nodejs` | ^4.1.254 | **~2-3 MB**(巨型 SDK) | 腾讯云短信 | 服务端 | P0 |
+| `exceljs` | ^4.4.0 | ~250-300 KB | Excel 导入导出 | 服务端 | P1 |
+| `@tiptap/*`(5 包 + tiptap-markdown) | 3.x | ~180-220 KB | 富文本编辑器(3 处) | 客户端 | P0 |
+| `@dnd-kit/*`(core+sortable+utilities) | 6.x/10.x | ~30-40 KB | 拖拽 | 客户端 | P2 |
+| `lucide-react` | ^0.562.0 | 单图标 ~1 KB,全集 ~800 KB | 图标库(100+ 处) | 客户端 | P1 |
+| `radix-ui/*`(16 个包) | 各版本 | 单包 ~3-8 KB | UI 原语 | 客户端 | P2 |
+| `openai` | ^6.25.0 | ~60-80 KB | AI SDK | 服务端 | P2 |
+| `react-markdown` + remark-* + rehype-* | 4.x-10.x | ~50-70 KB | Markdown 渲染 | 客户端 | P2 |
+| `@tanstack/react-query` + `react-table` | 5.x/8.x | ~35-45 KB | 数据层 | 客户端 | P3 |
+
+### 1.2 "use client" 指令分布
+
+| 指标 | 数值 |
+|------|------|
+| `"use client"` 文件总数 | **553** |
+| `src/**/*.{ts,tsx}` 文件总数 | >700 |
+| 客户端组件占比(估算) | **~70-75%** |
+| `shared/components/ui/` 下 "use client" 文件 | 40 个 |
+
+**典型反例**:
+
+| 文件 | 问题 | 严重度 |
+|------|------|--------|
+| `src/shared/components/ui/status-badge.tsx:1` | 仅渲染 Badge 无 hook 无事件,过度标记为 client | P2 |
+| `src/shared/components/ui/label.tsx:1` | 仅 forwardRef + cva,可考虑改 server | P3 |
+
+### 1.3 动态导入使用情况
+
+| 模式 | 数量 | 位置 |
+|------|------|------|
+| `next/dynamic` | **2** | `force-graph.tsx:18`、`homework-assignment-exam-error-explorer-lazy.tsx:5` |
+| `React.lazy` | **0** | 无 |
+| 服务端动态 `import()` | 1 | `sms-channel.ts:186`(tencentcloud) |
+
+**应使用但未使用动态导入的重型组件**:
+
+#### [P0] ReactFlow(@xyflow/react)未动态导入
+- 位置:`src/modules/textbooks/components/knowledge-graph.tsx:5-14`
+- 代码:`import { ReactFlow, Background, ... } from "@xyflow/react"` 顶层静态导入
+- 影响路由:`teacher/textbooks/[id]` 与 `student/learning/textbooks/[id]`
+- 估算影响:每个 textbook 路由 chunk 增加 ~120-140 KB
+- 修复参考:`force-graph.tsx:74-80` 已有的 `dynamic(() => import(...), { ssr: false, loading: ... })` 模式
+
+#### [P0] tiptap 富文本编辑器未动态导入(3 处)
+- 位置 1:`src/shared/components/ui/rich-text-editor.tsx:5-8`
+- 位置 2:`src/modules/exams/editor/exam-rich-editor.tsx:4-6`
+- 位置 3:`src/modules/lesson-preparation/components/blocks/rich-text-block.tsx:3-6`
+- 影响路由:`teacher/exams/[id]/edit-rich`、`teacher/exams/new`、教材阅读页链路、备课编辑页
+- 估算影响:每个含编辑器路由增加 ~180-220 KB
+
+### 1.4 大体积第三方库使用模式
+
+#### lucide-react(100+ 处使用)
+- ✅ 全部使用具名导入 `import { IconName } from "lucide-react"`
+- ❌ 缺少 `experimental.optimizePackageImports: ["lucide-react"]`(Turbopack 内建 tree-shaking 可能不彻底)
+- 严重度:P1
+
+#### recharts(13 处使用)
+- ✅ 业务侧具名导入
+- ❌ [P1 反例] `src/shared/components/ui/chart.tsx:5` → `import * as RechartsPrimitive from "recharts"` barrel 导入
+- 影响:chart.tsx 被 12 个图表组件引用,barrel 导入让 Turbopack 难以精确剔除未用子模块
+- 修复:改为按需具名导入
+
+#### @xyflow/react(4 处使用,仅 textbooks 模块)
+- 模块隔离良好 ✅
+- 但未动态导入导致 textbook 路由 chunk 膨胀 ❌
+
+#### tencentcloud-sdk-nodejs(服务端专用,已正确隔离)
+- 位置:`src/modules/notifications/channels/sms-channel.ts:186`
+- 模式:`await import("tencentcloud-sdk-nodejs")` ✅
+- [P0] 建议追加 `serverExternalPackages: ["tencentcloud-sdk-nodejs"]` 防止误打包
+
+#### exceljs(1 处使用)
+- 位置:`src/shared/lib/excel.ts:3`
+- 模式:静态 `import ExcelJS from "exceljs"`(值导入)
+- 风险:若被 client 组件误引用将导致 250-300 KB 打包
+
+### 1.5 next.config.ts 配置审计
+
+**当前完整内容**(11 行):
+
+```ts
+const nextConfig: NextConfig = {
+ output: "standalone",
+ serverExternalPackages: ["mysql2"],
+};
+```
+
+**缺失配置项**:
+
+| 缺失配置 | 影响 | 严重度 |
+|---------|------|--------|
+| `experimental.optimizePackageImports` | lucide-react/recharts/@xyflow/react/@radix-ui/* /@tiptap/* 未启用按需优化 | P0 |
+| `serverExternalPackages` 未含 `tencentcloud-sdk-nodejs` | 2-3 MB SDK 误打包风险 | P0 |
+| `serverExternalPackages` 未含 `exceljs` | 250-300 KB 误打包风险 | P1 |
+| 自定义 webpack/turbopack manualChunks | 无 splitChunks 自定义 | P2 |
+| `experimental.webVitalsAttribution` | 无法精确定位 Web Vitals 来源元素 | P2 |
+
+**建议补充配置**:
+
+```ts
+const nextConfig: NextConfig = {
+ output: "standalone",
+ serverExternalPackages: ["mysql2", "tencentcloud-sdk-nodejs", "exceljs"],
+ experimental: {
+ optimizePackageImports: [
+ "lucide-react",
+ "recharts",
+ "@xyflow/react",
+ "@tiptap/react",
+ "@tiptap/starter-kit",
+ "@tiptap/extension-placeholder",
+ "@tiptap/extension-image",
+ "@radix-ui/react-dialog",
+ "@radix-ui/react-dropdown-menu",
+ "@radix-ui/react-select",
+ // ... 其余 radix 包
+ ],
+ },
+};
+```
+
+### 1.6 Barrel File 风险
+
+| 路径 | 导出 | 风险 |
+|------|------|------|
+| `src/shared/components/ui/` | 无 index.ts barrel | ✅ 良好 |
+| `src/modules/exams/editor/index.ts` | ExamRichEditor + 6 个 tiptap 扩展 + 类型 | **P0 高风险** |
+| `src/modules/exams/ai-pipeline/index.ts` | — | 低 |
+| `src/shared/hooks/index.ts` | 7 个 hook | 低 |
+
+**[P0] exams/editor/index.ts 高风险 barrel**
+- 位置:`src/modules/exams/editor/index.ts:1-26`
+- 已造成实际问题:`src/app/(dashboard)/teacher/exams/[id]/edit-rich/page.tsx:9-14` 已有显式注释绕过 barrel
+- 但同文件第 15 行同步导入 `ExamRichForm`(其内部第 22-32 行又通过 barrel 导入)→ 绕过失效
+- 错误信息:`'Class extends value undefined is not a constructor or null'`(Server Component 连带加载 @tiptap/react 客户端扩展)
+- 修复方向:拆分为 `editor/types.ts`(仅类型,可被 server 引用)+ `editor/index.ts`(仅值导出,禁止 server 引用)
+
+### 1.7 图片/字体/静态资源
+
+| 资源 | 现状 | 评估 |
+|------|------|------|
+| `next/image` 使用 | 4 处(exam-preview、scan-uploader、scan-image-viewer、exam-rich-form) | ✅ 良好 |
+| 原生 `
` | 1 处(scan-image-viewer.tsx:143,已加 eslint-disable 注释) | ⚠️ 见 CLS-1 |
+| `next/font` | 1 个(Inter,`display: "swap"` + `variable`) | ✅ 良好 |
+| `manifest.ts` | 完整 | ✅ 良好 |
+| `public/` | 5 个 SVG | ✅ 良好 |
+
+### 1.8 Bundle 预算建议基线表
+
+> 因当前 `next build` 失败无法获取实际产物体积,以下为基于依赖估算的 K12 教务系统推荐基线(gzip 后)。
+
+| 路由类别 | First Load JS 阈值 | 当前估算 | 严重度 |
+|---------|-------------------|---------|--------|
+| 落地页 / 登录 / 注册(`(auth)/*`) | ≤ 180 KB | ~150-180 KB | P2 |
+| 学生端 Dashboard | ≤ 250 KB | ~250-320 KB | P2 |
+| 教师端 Dashboard | ≤ 280 KB | ~280-350 KB | P2 |
+| 列表/详情页(公告/作业/成绩等) | ≤ 300 KB | ~300-400 KB(recharts 拉入) | P1 |
+| **教材阅读页**(含 ReactFlow + tiptap + force-graph) | ≤ 600 KB | **~700-900 KB**(全量静态导入) | **P0** |
+| **试卷富文本编辑页**(含 tiptap 全家桶) | ≤ 500 KB | **~600-750 KB** | **P0** |
+| **备课编辑页**(含 tiptap + dnd-kit) | ≤ 550 KB | ~600-800 KB | **P0** |
+| 单路由增量 chunk(动态加载组件) | ≤ 200 KB | force-graph 单独 chunk ~80 KB ✅ | — |
+| 共享框架 chunk(react/react-dom/next) | ≤ 130 KB | ~130 KB(标准) | — |
+| 单文件体积上限(lint 规则) | ≤ 1000 行 | 已有项目规则约束 | — |
+
+---
+
+## 第二部分:Core Web Vitals 审计
+
+### 2.1 LCP(最大内容绘制)
+
+#### [P1] LCP-1:根 layout.tsx 串行 await
+- **位置**:`src/app/layout.tsx:45-50`
+- **问题**:每个 SSR 请求串行调用 `getLocale()` → `getMessages()` → `auth()`,三者无数据依赖
+- **影响**:TTFB、LCP、FCP(每个页面渲染都被推迟)
+- **修复**:`const [locale, messages, session] = await Promise.all([getLocale(), getMessages(), auth()])`
+
+#### [P1] LCP-3:teacher/student/parent/management 根路由缺失 loading.tsx
+- **问题**:通过 Glob 搜索 `**/loading.tsx` 仅找到 `admin/loading.tsx`,4 个角色根路由段下无 loading.tsx
+- **影响**:角色切换/首次进入时白屏闪烁,LCP/CLS 退化
+- **建议**:为 4 个角色根路由分别创建 `loading.tsx`,复用 `DashboardLoadingSkeleton`
+
+#### [P1] LCP-5:force-dynamic 滥用,无 ISR 缓存收益
+- **问题**:全项目共 **96 处** `export const dynamic = "force-dynamic"`,即使是相对静态的页面(学校列表、部门列表、教材目录)也强制动态
+- **影响**:每请求都重新 SSR + DB 查询,TTFB/LCP 无静态缓存命中加速
+- **建议**:对低频变更页面用 `revalidate = 3600` 替代 `force-dynamic`
+
+#### [P2] LCP-2:备课编辑器 textbook → chapters 数据瀑布
+- **位置**:`src/app/(dashboard)/teacher/lesson-plans/[planId]/edit/page.tsx:34-46`
+- **问题**:第 34-46 行串行调用 `getTextbookById()` → `getChaptersByTextbookId()`
+- **参考**:`parent/lesson-plans/[planId]/view/page.tsx:49-52` 已修复为 `Promise.all`
+
+#### 正面发现
+- ✅ `teacher/lesson-plans/page.tsx`、`teacher/exams/[id]/build/page.tsx`、`teacher/exams/[id]/analytics/page.tsx`、`parent/lesson-plans/[planId]/view/page.tsx` 均用 `Promise.all`
+- ✅ 4 个角色 dashboard 均用 React `use()` + Suspense 流式渲染
+- ✅ `DashboardSection` 封装 `SectionErrorBoundary + Suspense + 骨架屏`,分区流式渲染
+
+### 2.2 CLS(累计布局偏移)
+
+#### [P2] CLS-1:scan-image-viewer.tsx 使用 `
` 且未指定宽高
+- **位置**:`src/modules/homework/components/scan-image-viewer.tsx:142-148`
+- **问题**:原生 `
`(已加 eslint-disable 注释)仅设 `maxHeight: "80vh"`,无 width/height 也无 aspect-ratio
+- **影响**:图片加载完成时推动布局
+- **修复**:用 `next/image` 替换(同文件第 194-200 行缩略图已正确用 `next/image fill sizes="48px"`),或为 `
` 加 `aspect-ratio` 内联样式
+
+#### 正面发现
+- ✅ 字体配置正确(`display: "swap"` + `variable`)
+- ✅ `