20 KiB
20 KiB
Frontend Engineering Standards (Next_Edu)
Status: ACTIVE
Owner: Frontend Team
Scope: Next.js App Router 前端工程规范(编码、目录、交互、样式、数据流、质量门禁)
Applies To: src/app/*, src/modules/*, src/shared/*, docs/design/*
0. 目标与非目标
0.1 目标
- 让新加入的前端工程师在 30 分钟内完成对齐并开始稳定迭代
- 保证 UI 一致性(Design Token + Shadcn/UI 复用)
- 充分利用 App Router + RSC(Server-First)降低 bundle、提升性能
- 保证类型安全与可维护性(Vertical Slice、数据访问边界清晰)
- 形成可执行的质量门禁(lint/typecheck/build 与评审清单)
0.2 非目标
- 不规定具体业务模块的需求细节(业务规则以
docs/design/*与 PRD 为准) - 不引入与当前仓库技术栈不一致的新框架/库(新增依赖需明确收益与替代方案)
1. 接手流程(Onboarding Checklist)
1.1 先读什么(按顺序)
- 设计系统与 UI 规范:docs/design/design_system.md
- 角色路由与目录规范:docs/architecture/002_role_based_routing.md
- 项目架构总览:ARCHITECTURE.md
- 你将要改动的模块实现文档:
docs/design/00*_*.md
1.2 开发前对齐(必须)
- 核对 Design Tokens 与暗色模式变量:
- Tailwind 语义色映射:tailwind.config.ts
- CSS 变量定义:src/app/globals.css
- 盘点可复用 UI 组件:
src/shared/components/ui/* - 盘点通用工具(
cn等):src/shared/lib/utils.ts
1.3 环境变量与配置校验(必须)
- 统一使用
@t3-oss/env-nextjs的env入口读取环境变量,禁止在业务代码中散落process.env.* - Schema 定义与校验入口:src/env.mjs
- 任何新增环境变量:
- 必须先在
src/env.mjs增加 schema - 必须在 docs 中更新部署/运行说明(就近更新对应模块文档或全局架构文档)
- 必须先在
1.3 本地跑通(推荐顺序)
- 安装依赖:
npm install - 启动开发:
npm run dev - 质量检查:
npm run lintnpm run typechecknpm run build
2. 核心工程原则(必须遵守)
2.1 Vertical Slice(按业务功能组织)
- 业务必须放在
src/modules/<feature>/* src/app/*是路由层,只负责:- 布局组合(layout)
- 读取
searchParams/params - 调用模块的数据访问函数(
data-access.ts) - 组合模块组件渲染
- 通用能力放在
src/shared/*:- 通用 UI:
src/shared/components/ui/* - 通用工具:
src/shared/lib/* - DB 与 schema:
src/shared/db/*
- 通用 UI:
2.2 Server-First(默认 Server Component)
- 默认写 Server Component
- 只有在需要以下能力时,才把“最小子组件”标记为 Client Component:
useState/useEffect/useMemo(与交互/浏览器相关)- DOM 事件(
onClick/onChange等) useRouter/usePathname等客户端导航 hooks- Radix/Portal 类组件需要客户端(Dialog/Dropdown 等通常在 client 内组合使用)
2.3 不重复造轮子(Shadcn/UI 优先)
- 禁止手写 Modal/Dropdown/Tooltip 等基础交互容器
- 优先组合
src/shared/components/ui/*(Button/Card/Dialog/DropdownMenu/AlertDialog/Skeleton/EmptyState 等) - 若现有基础组件无法满足需求:
- 优先通过 Composition 在业务模块里封装“业务组件”
- 仅在存在 bug 或需要全局一致性调整时,才考虑改动
src/shared/components/ui/*(并在 PR 中明确影响面)
2.4 Client Component 引用边界(强制)
- 禁止在 Client Component 中导入任何“服务端实现”代码(例如 DB 实例、data-access、server-only 模块)
- Client Component 允许导入:
src/shared/components/ui/*(基础 UI)src/shared/lib/*(纯前端工具函数)- Server Actions(
"use server"导出的 action 函数) - 类型定义必须使用
import type(避免把服务端依赖带入 client bundle)
- 所有
data-access.ts必须包含import "server-only",并将其视为强制安全边界(不是可选优化)
3. 目录与路由规范
3.1 路由目录(App Router)
- 认证域:
src/app/(auth)/* - 控制台域(共享 App Shell):
src/app/(dashboard)/* - 角色域:
src/app/(dashboard)/teacher|student|admin/* /dashboard作为入口页(重定向/分发到具体角色 dashboard):参考 002_role_based_routing.md
3.2 页面文件职责
page.tsx:页面组装(RSC),不承载复杂交互loading.tsx:路由级加载态(Skeleton)error.tsx:路由级错误边界(友好 UI)not-found.tsx:路由级 404
3.3 错误处理与用户反馈(Error Handling & Feedback)
- 路由级错误(404/500/未捕获异常):
- 交由
not-found.tsx/error.tsx处理 - 禁止在
error.tsx里弹 Toast 堆栈刷屏,错误页输出必须友好且可恢复(例如提供 retry / 返回入口)
- 交由
- 业务操作反馈(表单提交/按钮操作/行级动作):
- 统一由 Client Component 在调用 Server Action 后触发
sonnertoast - 只在“成功”或“明确失败(业务/校验错误)”时触发 toast;未知异常由 action 归一为失败 message
- 统一由 Client Component 在调用 Server Action 后触发
3.4 异步组件与 Suspense(Streaming)
- 对于数据加载超过 300ms 的非核心 UI 区块(例如:仪表盘某张统计卡片/图表/第三方数据块):
- 必须用
<Suspense fallback={<Skeleton />}>包裹,以避免全页阻塞
- 必须用
- 禁止在
page.tsx顶层用多个串行await造成瀑布请求:- 多个独立请求必须使用
Promise.all - 或拆分为多个 async 子组件并行流式渲染(用
Suspense分段展示)
- 多个独立请求必须使用
3.3 动态渲染策略(避免 build 阶段查库)
当页面在渲染时会查询数据库或依赖 request-time 数据,且无法安全静态化时:
- 在页面入口显式声明:
export const dynamic = "force-dynamic"
- 该策略已用于教师端班级与作业相关页面,见相应 design 文档(例如教师班级模块更新记录)
4. 模块内文件结构(强制)
每个业务模块使用统一结构(可按复杂度增减,但命名必须一致):
src/modules/<feature>/
├── components/ # 仅该模块使用的 UI 组件(可含 client 组件)
├── actions.ts # Server Actions(写入/变更 + revalidatePath)
├── data-access.ts # 数据查询与聚合(server-only + cache)
├── schema.ts # Zod schema(若需要)
└── types.ts # 类型定义(与 DB/DTO 对齐)
约束:
actions.ts必须包含"use server"data-access.ts必须包含import "server-only"(防止误导入到 client bundle)- 复杂页面组件必须下沉到
src/modules/<feature>/components/*,路由层只做组装
5. Server / Client 边界与拆分策略
5.1 最小化 Client Component 的落地方式
- 页面保持 RSC
- 把需要交互的部分抽成独立
components/*子组件并标记"use client" - Client 组件向上暴露“数据变化事件”,由 Server Action 完成写入并
revalidatePath
5.4 Hydration 一致性(必须)
- 所有 Client Component 的首屏渲染必须保证与 SSR 产出的 HTML 一致
- 禁止在 render 分支中使用:
typeof window !== "undefined"之类的 server/client 分支Date.now()/Math.random()等不稳定输入- 依赖用户 locale 的时间格式化(除非服务端与客户端完全一致并带 snapshot)
- 对于 Radix 等组件生成的动态 aria/id 导致的属性差异:
- 优先通过组件封装确保首屏稳定
- 若确认差异不可避免且不影响交互,可在最小范围使用
suppressHydrationWarning
5.2 页面必须只做“拼装”,功能模块必须独立
- 任何功能模块都必须在
src/modules/<feature>/components/*内独立实现 page.tsx只负责:- 读取
params/searchParams - 调用
data-access.ts获取数据 - 以组合方式拼装模块组件(不在 page 内实现具体交互与复杂 UI)
- 读取
- 行数不是拆分依据,只是“路由层变厚”的信号;一旦出现成块的功能 UI,应立即下沉到模块组件
5.3 什么时候允许在 Client 中做“局部工作台”
当交互复杂到“页面需要类似 SPA 的局部体验”,允许将工作台容器作为 Client:
- 典型场景:三栏工作台、拖拽排序编辑器、复杂筛选器组合、富交互表格
- 但仍要求:
- 初始数据由 RSC 获取并传入 Client
- 写操作通过 Server Actions
- UI 状态尽量 URL 化(能分享/回溯)
6. 样式与 UI 一致性(Design System 强制项)
6.1 Token 优先(语义化颜色/圆角)
- 颜色必须使用语义 token:
bg-background,bg-card,bg-muted,text-foreground,text-muted-foreground,border-border等
- 禁止硬编码颜色值(
#fff/rgb())与随意引入灰度(如bg-gray-100) - 圆角、边框、阴影遵循设计系统:
- 常规组件使用
rounded-md等语义半径(由--radius映射)
- 常规组件使用
6.2 className 规范
- 所有条件样式必须使用
className={cn(...)} cn入口为@/shared/lib/utils
6.3 禁止 Arbitrary Values(默认)
- 默认禁止
w-[123px]等任意值 - 只有在设计系统或现有实现明确允许、并且无法用 token/栅格解决时,才可使用,并在 PR 描述说明原因
6.4 微交互与状态(必须有)
- 按钮 hover:必须有 transition(现有 Button 组件已内置)
- 列表项 hover:使用
hover:bg-muted/50等轻量反馈 - Loading:必须使用
Skeleton(路由级loading.tsx或组件内 skeleton) - Empty:必须使用
EmptyState - Toast:统一使用
sonner
7. 图标规范(lucide-react)
- 统一使用
lucide-react - 图标尺寸统一:默认
h-4 w-4,需要强调时h-5 w-5 - 颜色使用语义化:例如
text-muted-foreground
8. 数据流规范(查询、写入、状态)
8.1 查询(data-access.ts)
- 所有查询放在
src/modules/<feature>/data-access.ts - 需要复用/去重的查询优先用
cache包裹(React cache) - 查询函数返回“UI 直接可消费的 DTO”,避免页面层再做复杂映射
8.2 写入(actions.ts)
- 所有写操作必须通过 Server Actions
- 每个 action:
- 校验输入(Zod 或手写 guard)
- 执行 DB 写入
- 必须
revalidatePath(以页面为单位)
8.3 Server Action 返回结构(统一反馈协议)
- 所有 Server Action 必须返回统一结构,用于前端统一处理 toast 与表单错误
- 统一使用类型:src/shared/types/action-state.ts
export type ActionState<T = void> = {
success: boolean
message?: string
errors?: Record<string, string[]>
data?: T
}
约束:
errors必须对齐zod的error.flatten().fieldErrors结构- 禁止在各模块内重复定义自有的 ActionState 类型
8.4 Toast 触发时机(强制)
- Client Component 在调用 Server Action 后:
success: true:触发toast.success(message)(或使用模块内约定的成功文案)success: false:- 存在
errors:优先渲染表单字段错误;可选触发toast.error(message) - 不存在
errors:触发toast.error(message || "Action failed")
- 存在
- 对于路由级异常与边界错误,禁止用 toast 替代
error.tsx
8.5 URL State(nuqs 优先)
- 列表页筛选/分页/Tab/排序等“可分享状态”必须放 URL
- 使用
nuqs做类型安全的 query state 管理
8.6 Data Access 权限边界(Security / IDOR 防护)
data-access.ts不是纯 DTO 映射层,必须承担数据归属权校验- 允许两种合规方式(二选一,但模块内必须统一):
- 方式 A(强制传参):所有 data-access 函数显式接收
actor(userId/role)并在查询条件中约束归属(例如 teacherId) - 方式 B(函数内获取):data-access 函数首行获取 session/user 并校验 role/归属,再执行查询
- 方式 A(强制传参):所有 data-access 函数显式接收
- 禁止把权限校验放在 page.tsx 或 client 组件中作为唯一屏障
9. 数据完整性与 Seed 规则(禁止 Mock)
项目默认不使用 Mock 数据。
当某功能缺失实际数据,开发者必须把数据补齐到数据库与种子数据中,而不是在前端临时模拟。
执行规范:
- 若缺失的是“表结构/字段/关系”:
- 修改
src/shared/db/schema.ts与src/shared/db/relations.ts(按既有模式) - 生成并提交 Drizzle migration(
drizzle/*.sql)
- 修改
- 若缺失的是“可演示的业务数据”:
- 更新
scripts/seed.ts,确保npm run db:seed可一键生成可用数据
- 更新
- 文档同步(必须):
- 在 schema-changelog.md 记录本次新增/变更的数据表、字段、索引与外键
- 在对应模块的
docs/design/00*_*.md中补充“新增了哪些数据/为什么需要/如何验证(db:seed + 页面路径)”
9.1 Seed 分层(降低阻塞)
- Seed 分为两类:
- Baseline Seed:全项目必备的最小集合(核心用户/角色/基础字典数据等),保证任何页面都不因“数据空”而无法进入流程
- Scenario Seed(按模块):面向具体模块的可演示数据包(例如:班级/题库/试卷/作业),用于复现与验证该模块交互
- 任何模块新增数据依赖,必须以 “Scenario Seed” 的形式落到
scripts/seed.ts,而不是把数据要求隐含在前端逻辑里
9.2 Seed 可复现与数据锚点(保证跨模块联动)
- Seed 必须可重复执行(idempotent),避免开发环境多次执行后产生脏数据与重复数据
- 对跨模块联动依赖的关键实体,必须提供可稳定引用的数据锚点:
- 固定标识(如固定 email/slug/title 组合)或可预测 ID(按现有 seed 约定)
- 文档必须写明锚点是什么、依赖它的模块有哪些、如何验证
- 禁止在 UI 里依赖“随机生成数据顺序”来定位实体(例如 “取第一条记录作为 demo 用户” 这类逻辑应退化为明确锚点)
9.3 外部服务的例外(仅限 Adapter Mock)
- 内部业务数据严格遵守“DB + Migration + Seed”,不允许 Mock
- 仅当对接外部不可控服务(支付/短信/第三方 AI 流式等)且无法用本地 seed 复现时:
- 允许在
src/shared/lib/mock-adapters/*建立 mock 适配器 - 必须先定义 Adapter 接口,再提供真实实现与 mock 实现(业务模块只能依赖接口,不可直接依赖某个具体实现)
- 该 mock 仅用于外部服务交互层,禁止承载内部业务数据
- 允许在
10. 表单规范(react-hook-form + zod)
- 表单统一使用
react-hook-form+@hookform/resolvers+zod - 错误提示放在输入框下方:
- 字号
text-xs - 颜色
text-destructive
- 字号
- 破坏性操作必须二次确认(
AlertDialog) - 提交中按钮禁用并展示 loading(可使用
useFormStatus或本地 state)
11. 质量门禁与评审清单(PR 必须过)
11.1 本地必须通过
npm run lintnpm run typechecknpm run build
11.2 代码评审清单(Reviewer 逐项检查)
- 目录结构是否符合 Vertical Slice(路由层是否保持“薄”)
- 页面是否只做拼装(功能 UI 是否全部下沉到模块组件)
- Server/Client 边界是否最小化(是否把整页误标 client)
- 是否复用
src/shared/components/ui/*,是否重复实现基础交互 - 是否使用语义化 token(颜色/圆角/间距),是否引入硬编码颜色与大量 arbitrary values
- Loading/Empty/Error 是否齐全(Skeleton/EmptyState/error.tsx)
- 列表页筛选是否 URL 化(nuqs),是否支持刷新/分享
- 写操作是否通过 Server Action 且正确
revalidatePath - 是否避免 Mock(数据是否通过迁移 + seed 补齐,且 docs/db 与模块文档已同步)
- 是否引入不必要的依赖与重型客户端逻辑
11.3 Commit 规范(Git History)
- 推荐遵循 Conventional Commits:
feat:新功能fix:修复 bugdocs:文档更新refactor:重构(无功能变化)chore:工程杂项
- 约束:
- 单次提交必须聚焦一个意图,避免把大范围格式化与功能修改混在一起
- 涉及 DB 迁移与 seed 变更时,commit message 必须包含模块/领域关键词,便于追溯
12. 文档同步规则(Docs Sync)
以下情况必须同步更新文档(就近放在 docs/design/* 或 docs/architecture/*):
- 新增“全局交互模式”(例如:新的工作台/拖拽范式/跨模块复用交互)
- 新增“全局组件”或改变基础 UI 行为(影响
src/shared/components/ui/*) - 新增关键路由结构或权限/角色策略
12.1 业务组件可发现性(可选但推荐)
- 对
src/modules/<feature>/components内的复杂业务组件(例如:试卷编辑器、排课表、工作台):- 推荐在对应的
docs/design/00*_*.md增加“用法示例 + 关键 props + 截图” - 若团队资源允许,可引入 Storybook 作为可视化组件目录(不作为硬性门禁)
- 推荐在对应的
13. Performance Essentials(必须遵守)
- 图片:
- 强制使用
next/image替代<img>(SVG 或已明确无需优化的极小图标除外) - 头像等外部域名资源必须配置并明确缓存策略
- 强制使用
- 字体:
- 强制使用
next/font管理字体加载 - 禁止在 CSS 中
@import外部字体 URL(避免 CLS 与阻塞渲染)
- 强制使用
- 依赖:
- 禁止引入重型动画库作为默认方案;复杂动效需按需加载并解释收益
- 大体积 Client 组件必须拆分与动态加载,并通过
Suspense提供 skeleton fallback
14. 参考实现(从现有代码学习的路径)
- 设计系统与 UI 组件清单:docs/design/design_system.md
- Auth:路由层 RSC + 表单 client 拆分模式:docs/design/001_auth_ui_implementation.md
- 教师端班级模块:URL state + client 交互组件 + server actions 的组合:docs/design/002_teacher_dashboard_implementation.md
- 教材工作台:RSC 拉初始数据 + client 工作台容器接管交互:docs/design/003_textbooks_module_implementation.md
- 题库:nuqs 驱动筛选 + TanStack Table + CRUD actions:docs/design/004_question_bank_implementation.md
- 考试组卷:拖拽编辑器(@dnd-kit)+ structure JSON 模型:docs/design/005_exam_module_implementation.md
- 作业:冻结 exam → assignment 的域模型 + 学生作答/教师批改闭环:docs/design/006_homework_module_implementation.md