写在前面(学习笔记)
- 这是一份我搭全栈项目时的目录结构小抄,记录我理解到的“为什么这么分”和“怎么落地”。
- 我更看重改动路径是否短、边界是否清晰、测试是否就近能写,而不是追求“最标准模板”。
- 文中给的结构只是起点,实际项目按团队规模与变更频率微调就好。
一句话心法:目录结构服务“变更”,谁经常一起改就放一起,稳定的沉到底,边界清楚可测试。
核心原则(记住 6 件事)
- 关注点分离:同类职责放一起,不同生命周期的职责分开。
- 内聚优先于解耦:模块内部变化要同向;跨模块依赖保持单向和最小化。
- 按变更率收敛:一起频繁改的放一起;稳定层沉到底。
- 明确边界:运行时边界(server/client)、部署边界(app/service/package)、团队边界(ownership)。
- 可演进:支持重组与下沉,避免早期过度抽象。
- 可测试:结构天然支持单测、组件测与集成/E2E 的就近放置与隔离。
常见目录组织模式
- 按功能(by-feature)
plaintext
12345678910111213
src/
features/
auth/
api/
ui/
lib/
tests/
billing/
dashboard/
shared/
ui/ # 基础组件
lib/ # 工具方法
types/
适合中大型前端/全栈项目;团队协作清晰,符合“垂直切片”。
- 按层(by-layer)
plaintext
12345
src/
app/ # 页面与路由
components/
services/ # 业务服务/数据访问
lib/
直观易懂,适合小团队/快速起步;规模变大后易耦合在同一层。
- 领域驱动(domain-driven)
plaintext
12345678
src/
domains/
user/
entities/
use-cases/
adapters/ # HTTP/DB 适配器
order/
platform/ # 跨域能力(日志、事件、缓存)
与业务边界强绑定,利于长期演进;前期设计成本更高。
- 垂直切片(vertical slice)
plaintext
1234567
src/
slices/
report-sales/ # 从页面到 API 到数据的一条龙
page/
api/
data/
tests/
减少跨目录跳转,交付速度快;复用抽象需要从 slice 中下沉到 shared。
Next.js 全栈推荐结构(App Router)
将“运行时边界(server/client)”与“变更边界(feature)”结合:
plaintext
1234567891011121314151617
src/
app/ # 路由与页面(服务器组件优先)
(marketing)/
(app)/
api/ # app/api 路由处理器
features/
auth/
ui/ # 客户端组件(use client)
server/ # 仅服务端逻辑:actions、服务、数据访问
api/ # 与 app/api 同构的逻辑封装(可复用)
tests/
billing/
shared/
ui/ # 基础可复用组件
lib/ # 工具、hooks、校验、格式化
config/ # 常量、env 解析
types/
配套约定:
- server-only 与 client-only 清晰标注;
use client仅在必要的 UI 入口声明。 - 数据访问通过可注入仓库接口(repository),UI 不直接触达 DB。
- API 处理器仅做编排:校验 → 调用 use-case/service → 返回结果。
示例:auth feature 的 API/服务划分
plaintext
123
src/features/auth/server/login.ts # use-case(服务端)
src/features/auth/api/login.ts # 供 app/api/route 与 RSC 调用
src/app/api/login/route.ts # 请求/响应编排
Monorepo 组织(多应用/多服务)
当存在多前端/边车服务/函数时,用 workspace + packages 提升复用:
plaintext
123456789
apps/
web/ # Next.js 应用(SSG/SSR)
admin/ # 后台或 BFF
worker/ # 边缘/队列任务
packages/
ui/ # 共享 UI 组件库(无 Next 依赖)
api-sdk/ # 前后端共享的 API 客户端/types/zod schema
config/ # ESLint/TS/BIOME/Prettier 等统一配置
db/ # schema 与迁移(如 drizzle/prisma)
建议:
- 包明确边界:
ui不依赖 Next;api-sdk只暴露 fetch/类型;db不泄漏连接细节。 - 通过
tsconfigpath 与exports控制依赖方向,防止“反向引用”。 - CI 按变更影响(affected)选择性构建与测试。
测试与文档的就近放置
- 单元测试:与源码同目录
*.test.ts,便于重构跟随。 - 组件测试:
features/*/ui/*.test.tsx,使用 RTL 与 JSDOM。 - API 集成测试:
features/*/api/*.test.ts,直接构造Request/Response。 - E2E:独立
e2e/,面向关键业务路径。 - 文档:每个 feature 放
README.md或 ADR(架构决策记录)。
命名与约定
- 目录名:小写-kebab,文件名与导出名一致。
- 公共与私有:
shared/*可复用;feature/*默认为内部实现,仅通过index.ts暴露公共 API。 - 配置集中:
shared/config统一 env 解析与常量,避免散落。 - 边界文件:
server/仅服务端可用;ui/仅 UI;在入口放置index.ts聚合导出。
反模式与避坑
- 巨型
utils/:将可业务命名的工具下沉到对应 feature/shared 子域。 - 横向耦合:不同 feature 互相导入实现细节 → 通过公共接口或事件解耦。
- 早期抽象:抽象以“第二次复用”为起点,不要为了可能的复用牺牲清晰度。
- Server/Client 混用:把
use client放在叶子节点,避免上层被迫成为客户端组件。
渐进式演进建议
- 先按层/小而清晰地开始 → 引入 features 目录收敛变更。
- 稳定复用抽象下沉到
shared/或packages/。 - 通过 ADR 记录一次结构性变更的动机与影响,方便团队共识。
小结
- 以“变更路径最短”为目标,结合 by-feature + 运行时边界划分。
- 在 Next.js 中坚持服务器组件优先,API 只做编排,服务与仓库聚焦业务。
- 大体量项目用 Monorepo + packages 提升复用与边界清晰度。