本节摘要:项目长大后的第一个问题是"代码放哪"。本节给出 Next.js 项目的推荐目录结构(按功能域组织)、分层设计(UI/数据/服务边界)、环境配置与团队规范,并总结一套"可维护性"的最佳实践清单。
阅读完本节,你应当能够:
项目几个月后,最大的成本是找代码——"这个功能在哪改?"目录混乱的项目,找代码的时间远超写代码。好的结构让"按功能找文件"成为直觉。
直觉类比:目录结构像"文件柜的分类"——按"功能"分类(auth、posts、admin)比按"技术"分类(components、lib、pages 各一堆)更符合业务直觉。找"登录相关的代码"去 auth/ 目录,而不是在 components/ 里翻。
💡 关键直觉:按功能域(feature)组织,而不是按技术类型组织——
app/里按路由分,components/里也按功能分(features/auth/、features/posts/)。同一功能的所有代码(组件、逻辑、样式)聚在一起,改一个功能只动一个目录。
my-app/ ├── app/ # 路由层(文件系统路由) │ ├── layout.tsx │ ├── page.tsx │ ├── (auth)/ │ │ ├── login/page.tsx │ │ └── register/page.tsx │ ├── dashboard/page.tsx │ ├── posts/ │ │ ├── page.tsx # 列表 │ │ └── [slug]/page.tsx # 详情 │ └── api/ # API 路由 │ └── posts/route.ts ├── components/ # 组件层(按功能域) │ ├── ui/ # 基础 UI(Button、Card) │ ├── features/ # 功能组件 │ │ ├── auth/ # 认证相关组件 │ │ └── posts/ # 文章相关组件 │ └── layouts/ # 布局组件 ├── lib/ # 数据与服务层 │ ├── db.ts # 数据库客户端 │ ├── auth.ts # 认证配置 │ └── actions/ # Server Actions │ ├── auth-actions.ts │ └── post-actions.ts ├── store/ # 全局状态(按需) ├── types/ # 共享类型 ├── public/ # 静态资源 ├── prisma/ # 数据库模型 └── next.config.ts

分层规则:
// lib/actions/post-actions.ts —— 按功能域分文件 "use server"; import { prisma } from "@/lib/db"; import { revalidatePath } from "next/cache"; export async function createPost(formData: FormData) { ... } export async function deletePost(id: number) { ... }
规范:Actions 按功能域分文件、集中在 lib/actions/,页面 import 使用。
.env*,用 lib/config.ts 统一读取;| 规范 | 做法 |
|---|---|
| 代码风格 | ESLint + Prettier 强制 |
| 类型 | TypeScript strict 模式 |
| 提交 | 约定式提交(feat/fix/docs) |
| 分支 | main 保护,PR 合并 |
| CI | lint + test + build 闸门 |
| 文档 | README + 架构说明 |
| 误区 | 现象 | 正解 |
|---|---|---|
| 按技术类型堆文件 | 找代码难 | 按功能域组织 |
| 组件里直接查库 | 逻辑散落、难测试 | 数据层 lib/ 集中 |
| 循环导入 | 报错 | 保持依赖单向 |
| Actions 散落各处 | 找不到、难维护 | 集中 lib/actions/ |
| 无 lint/类型检查 | 代码风格失控 | CI 强制 |
# 1. 按功能域建目录 mkdir -p components/features/{auth,posts} lib/actions store types # 2. 迁移组件 mv components/LoginForm.tsx components/features/auth/ mv components/PostCard.tsx components/features/posts/ # 3. 集中 Server Actions mv app/actions.ts lib/actions/post-actions.ts # 4. 统一数据访问 mv lib/db/client.ts lib/db.ts # 5. 用 lint + tsc 验证 npm run lint && npx tsc --noEmit
重构后:找"登录相关"去 components/features/auth/ + lib/actions/auth-actions.ts——每个功能的代码都在可预期的位置。
演进原则:不要一开始就设计"完美架构"——项目小的时候,结构简单最重要;当"找代码"开始变慢、改动影响面变大时,才分层重构。架构是长出来的,不是设计出来的。
规范三步走:先写约定(文档)→ 用工具固化(ESLint/Prettier)→ 进 CI 强制(不过不给合并)。靠自觉的规范会滑坡,工具强制的规范才持久。
一句话:架构的检验标准很简单——新同事(或三个月后的你)能否在十分钟内找到"加一个功能"该改哪些文件。能,架构就合格。
问:小项目也要按这个架构来吗?
不用。项目小时(几十个文件以内),简单的"app/ + components/ + lib/"就够了。架构随项目长大演进——过早分层是过度设计,找代码变慢时再分层。
问:Server Actions 放 lib/actions 还是页面目录?
建议集中放 lib/actions/ 按功能分文件(auth-actions、post-actions)。理由:Action 是"业务逻辑"而非"页面展示",集中便于复用与测试;页面只负责调用。
问:怎么避免循环导入?
保持依赖单向(路由层 → 组件层 → 数据层);扩展/工具放 lib/ 独立模块;页面不直接互相 import(用路由跳转代替)。
问:类型定义放哪?
共享类型放 types/ 目录;组件内私有类型放组件文件。Prisma 生成的类型(从 @prisma/client 导入)直接复用,不用重复定义。
问:重构的节奏?
每完成一个功能顺手整理;发现"放错位置"立即移动(小步提交);大重构用"先搭骨架、逐个迁移、每步可运行"推进。每次改动后应用都能跑。