初创团队为什么应该用 Monorepo:我们的实践
Monorepo 不只是大公司的专利。分享菅等村团队用 pnpm workspace 构建 monorepo 的实践经验。
当团队只有几个人甚至一个人时,代码组织方式看起来不是一个紧迫的问题。但经验告诉我们:项目组织方式在第一天就决定了未来迭代的顺滑度。
什么是 Monorepo
Monorepo(单体仓库)是指将多个项目放在同一个代码仓库中管理。与之相对的是 Polyrepo(多仓库),即每个项目一个独立仓库。
菅等村的 monorepo 结构包含:多个应用(apps/)和多个共享包(packages/),通过 pnpm workspace 管理。
为什么选择 Monorepo
对于小团队,monorepo 有几个显著优势:
1. 代码复用零成本
共享的逻辑提取为 packages 中的包,所有应用可直接引用。类型定义、工具函数、API 客户端等公共代码,一处修改、全处生效。不需要发包、不需要更新依赖版本。
2. 原子化提交
当你修改了一个共享函数的签名,可以在同一个 commit 中更新所有调用方。不存在"仓库 A 改了接口,仓库 B 还在用旧版"的问题。这对小团队快速迭代非常重要。
3. 统一工具链
ESLint、TypeScript、Prettier 等工具的配置只需维护一套。所有项目遵循相同的代码风格和质量标准。
4. 依赖管理简化
pnpm workspace 使用硬链接方式安装依赖,磁盘占用极低。所有项目共享一个 lockfile,依赖版本统一管理,不会出现版本不一致的问题。
我们的 Monorepo 结构
菅等村的 monorepo 包含以下结构:
- apps/app-web:发型试穿应用(Next.js)
- apps/app-photomeme:梗图生成器(Next.js)
- apps/app-official:品牌官网(本网站,Next.js)
- packages/common:共享类型、工具函数
- packages/storage:存储抽象层(Local/R2)
- packages/payment:支付抽象层(Paddle/Mock)
- packages/image-gen:图片生成抽象层(Replicate/Mock)
Provider 抽象模式
在 monorepo 中,我们采用 Provider 抽象模式构建共享包。以图片生成为例:定义一个 ImageGenProvider 接口,不同 AI 模型作为实现。工厂函数根据环境变量决定使用哪个实现。
这种模式的好处是:应用代码只依赖接口,不依赖具体实现。切换 AI 模型、存储方案或支付渠道时,只需要改环境变量,不需要改应用代码。
部署独立性
Monorepo 不意味着所有应用必须一起部署。我们为每个应用准备了独立的 Dockerfile 和 docker-compose 文件。可以单独构建、单独部署,按需上线。
这种"代码集中管理、部署各自独立"的模式,既保持了开发效率,又不影响部署灵活性。对小团队来说,这是最实用的平衡点。
总结
Monorepo 不是大公司的专利。恰恰相反,小团队更需要代码复用和工具链统一带来的效率提升。pnpm workspace 让 monorepo 的搭建和维护成本极低,而收益是持续和复利的。如果你的团队正在做多产品线,强烈建议从第一天就用 monorepo。