全栈项目目录结构:原则与实践指南

November, 2nd 2025 7 min read
全栈项目目录结构:原则与实践指南

写在前面(学习笔记)

  • 这是一份我搭全栈项目时的目录结构小抄,记录我理解到的“为什么这么分”和“怎么落地”。
  • 我更看重改动路径是否短、边界是否清晰、测试是否就近能写,而不是追求“最标准模板”。
  • 文中给的结构只是起点,实际项目按团队规模与变更频率微调就好。

一句话心法:目录结构服务“变更”,谁经常一起改就放一起,稳定的沉到底,边界清楚可测试。

核心原则(记住 6 件事)

  • 关注点分离:同类职责放一起,不同生命周期的职责分开。
  • 内聚优先于解耦:模块内部变化要同向;跨模块依赖保持单向和最小化。
  • 按变更率收敛:一起频繁改的放一起;稳定层沉到底。
  • 明确边界:运行时边界(server/client)、部署边界(app/service/package)、团队边界(ownership)。
  • 可演进:支持重组与下沉,避免早期过度抽象。
  • 可测试:结构天然支持单测、组件测与集成/E2E 的就近放置与隔离。

常见目录组织模式

  1. 按功能(by-feature)
plaintext
12345678910111213
      src/
  features/
    auth/
      api/
      ui/
      lib/
      tests/
    billing/
    dashboard/
  shared/
    ui/      # 基础组件
    lib/     # 工具方法
    types/
    

适合中大型前端/全栈项目;团队协作清晰,符合“垂直切片”。

  1. 按层(by-layer)
plaintext
12345
      src/
  app/      # 页面与路由
  components/
  services/ # 业务服务/数据访问
  lib/
    

直观易懂,适合小团队/快速起步;规模变大后易耦合在同一层。

  1. 领域驱动(domain-driven)
plaintext
12345678
      src/
  domains/
    user/
      entities/
      use-cases/
      adapters/       # HTTP/DB 适配器
    order/
  platform/           # 跨域能力(日志、事件、缓存)
    

与业务边界强绑定,利于长期演进;前期设计成本更高。

  1. 垂直切片(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 不泄漏连接细节。
  • 通过 tsconfig path 与 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 放在叶子节点,避免上层被迫成为客户端组件。

渐进式演进建议

  1. 先按层/小而清晰地开始 → 引入 features 目录收敛变更。
  2. 稳定复用抽象下沉到 shared/packages/
  3. 通过 ADR 记录一次结构性变更的动机与影响,方便团队共识。

小结

  • 以“变更路径最短”为目标,结合 by-feature + 运行时边界划分。
  • 在 Next.js 中坚持服务器组件优先,API 只做编排,服务与仓库聚焦业务。
  • 大体量项目用 Monorepo + packages 提升复用与边界清晰度。