返回探索
指南VibeFix 编辑部更新于 2026年10月17日

前端改了个类型,后端上线才炸:vibe 项目的 Monorepo 实战

三个信号说明你该 monorepo 了:一个功能要开 3 个 PR、类型对不上线才炸、AI agent 老改漏。这篇给你可直接抄的目录骨架、Turborepo 编排、类型共享实战,以及 Vercel 与 Docker 各自的部署坑。

图书馆高大书架的照片,摆满五颜六色的书籍,隐喻 monorepo 把多个包收纳在一个仓库里、井井有条

先划清边界:这篇不讲什么

仓库和流程的话题站内有三篇,先说明分工:

  • 《一人 AI 的 Git 工作流》讲的是「分支怎么玩」——main 直推还是 PR、AI 生成 commit 信息怎么审;
  • 《vibe 项目的开发环境标准化》讲的是「本地环境怎么统一」——devcontainer;
  • 《vibe 项目的 CI/CD 最小管线》讲的是「代码怎么自动上线」——5 个 Job。

本篇只回答一个问题:「代码放几个仓库、包之间怎么引用。」前端、后端、共享组件、数据库 schema——放在一个仓库里用 workspace 管起来,还是一人一个仓库?这是决定你未来一年开发体感的结构性选择。

三个信号:你该 monorepo 了

别为了「大厂也这么干」而上 monorepo。对照这三个信号,中了两条再动手:

  1. 跨仓库改同一个功能要开 3 个 PR。前端加个字段,后端、共享类型、文档各一个 PR,合并顺序还不能错——协调成本已经超过 monorepo 的学习成本;
  2. 「类型对不上」是线上 bug 的常见死因。后端改了 API 返回,前端还是旧类型,TypeScript 在各自仓库里都「编译通过」,上线才炸;
  3. AI agent 经常改漏。让 AI「把用户名字段改成可选」,它改了前端,忘了后端校验和数据库迁移——因为它一次只能看一个仓库。
多仓库(polyrepo)单体 monorepo
适合完全独立的产品线、需要独立权限/审计一人团队的前后端 + 共享包
跨包改动的原子性无:N 个 PR + 合并顺序有:一次提交全改完
类型一致性靠发版同步,滞后tsc 一次全查,错了当场报
CI 复杂度每个仓库一套一套,但要配 filter(见下文)
心智负担低(每个仓库简单)中(要理解 workspace 引用)

目录结构:一份能直接用的骨架

myapp/
├── apps/
│   ├── web/          # Next.js 前端
│   └── api/          # NestJS/FastAPI 后端
├── packages/
│   ├── ui/           # 共享 React 组件
│   ├── db/           # Prisma schema + 迁移 + client
│   ├── schemas/      # zod 校验 schema(前后端共用)
│   └── config/       # tsconfig / eslint 共享配置
├── pnpm-workspace.yaml
├── turbo.json
└── package.json
# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"

包之间用 workspace 协议互相引用,前端 package.json 里写 "@myapp/ui": "workspace:*",pnpm 会创建 symlink——本地改 packages/ui,apps/web 立刻生效,不用发版、不用 npm link 的玄学。

monorepo 包依赖拓扑框图:上层是 apps/web 与 apps/api,下层是 packages/ui、packages/db、packages/config,箭头标出包之间的依赖方向

Turborepo:让构建按依赖拓扑跑

monorepo 的构建不能 cd apps/web && npm run build 了事——packages/db 改了,api 和 web 都要重编。Turborepo 干的就是这个编排:

// turbo.json
{
  "$schema": "https://turborepo.dev/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],   // 先构建依赖包,再构建自己
      "outputs": ["dist/**", ".next/**"]
    },
    "db:migrate": {
      "dependsOn": ["^db:generate"],
      "cache": false             // 迁移永远真实执行,不缓存
    },
    "dev": { "cache": false, "persistent": true },
    "lint": { "dependsOn": ["^build"] }
  }
}

^build 的意思是「先跑我依赖包的 build」。你在根目录执行 turbo run build,它会按拓扑排序:config → db/schemas/ui → api/web,自动并行无依赖的任务。更重要的是缓存:没改过的包直接命中缓存,turbo run build 第二次跑只要几秒——AI agent 反复改代码时,这个缓存能省大量等待时间。

远程缓存(Vercel Remote Cache)一人团队免费额度够用,CI 里配上,构建时间再砍一半。

类型共享实战:让「对不上」在编译期就炸

这是 monorepo 对 vibe 项目最大的价值。三个共享点:

1. 数据库 schema 即类型源。packages/db 放 schema.prisma,prisma generate 产出的 client 被 api 和 web 同时引用。改字段类型:

// packages/db/package.json
{ "name": "@myapp/db", "exports": { ".": "./client.ts" } }
// packages/db/client.ts
import { PrismaClient } from '@prisma/client';
export const db = new PrismaClient();
export type { User, Post } from '@prisma/client';

2. API 契约前后端共用。packages/schemas 放 zod schema,后端用它校验入参,前端用它推导表单类型:

// packages/schemas/src/post.ts
import { z } from 'zod';
export const CreatePostSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1),
});
export type CreatePost = z.infer<typeof CreatePostSchema>;
// 后端:app.post('/posts', zValidator(CreatePostSchema))
// 前端:const data: CreatePost = form.getValues()  // 类型自动对齐

3. 根目录一次 tsc 全查。turbo run typecheck 跑所有包的 tsc --noEmit。后端改了返回字段但忘了改 schema,前端的类型错误会和后端的一起报出来——在提交前,不是在线上。

部署:两个平台各自的坑

Vercel(前端 apps/web):项目设置里 Root Directory 填 apps/web,Build Command 用 cd ../.. && turbo run build --filter=web...(...` 表示带上依赖包)。环境变量里加 TURBO_REMOTE_CACHE_SIGNATURE_KEY 开远程缓存。注意:Vercel 的 monorepo 支持只认 pnpm/npm/yarn workspace,packageManager 字段写死版本。

Docker(后端 apps/api):这是 monorepo 最大的坑——Docker 构建上下文。Dockerfile 必须放仓库根目录(context 是整个仓库),然后精确 COPY:

# 仓库根目录的 Dockerfile.api
FROM node:20-slim AS build
WORKDIR /app
# 只拷 api 及其依赖包,保持层缓存有效
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json turbo.json ./
COPY apps/api/package.json ./apps/api/
COPY packages/db/package.json ./packages/db/
COPY packages/schemas/package.json ./packages/schemas/
RUN npm i -g pnpm && pnpm install --frozen-lockfile \
    --filter @myapp/api...   # 只装 api 及其依赖
COPY apps/api ./apps/api
COPY packages/db ./packages/db
COPY packages/schemas ./packages/schemas
RUN pnpm --filter @myapp/api build

FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=build /app/apps/api/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER nonroot
ENTRYPOINT ["node", "dist/index.js"]

关键在 --filter @myapp/api...:pnpm 只安装 api 及其依赖包,不把 web 的 Next.js 也装进来。COPY 顺序同样遵循层缓存铁律:package.json 先,源码后(呼应本轮 g1 的 Dockerfile 指南)。

CI:只构建受影响的包

monorepo 的 CI 如果每次全量构建,改一行文案也要等 10 分钟。Turborepo 的 filter 解决:

# .github/workflows/ci.yml 片段
- run: pnpm turbo run build lint typecheck --filter=...[HEAD^]
# ...[HEAD^] = 本次提交影响到的包及其依赖/被依赖者

只改了 apps/web 的样式?CI 只跑 web 的 build+lint。改了 packages/db 的 schema?api 和 web 的 typecheck 自动被带上——影响范围由依赖图决定,不用人脑记。这正是 monorepo 在 CI 上的复利:改得越多,省得越多…反过来,改得越少,跑得越快。

版本发布:一人团队的 changesets 轻量用法

packages 将来要发 npm 包?先别上 changesets 全套。一人团队够用方案:所有包 version: 0.0.0 占位,workspace 协议引用不依赖版本号;真要对外发布那天,再 pnpm changeset init。过早引入版本管理流程,是一个人能犯的最贵的「流程税」。

4 个坑:提前知道就不叫坑

  1. 幽灵依赖(phantom dependency)。pnpm 默认严格隔离,a 包没声明却用了 b 包的代码——在 npm 下能跑,在 pnpm 下炸。修法:缺啥在用到的包里显式声明,别靠 hoisting 的运气。
  2. IDE 跳转进 .d.ts 而不是源码。packages 的 exports 指向 dist,Ctrl+点击跳到编译产物。修法:开发期用 publishConfig + exports 条件导出,或配 tsconfig paths 指向 src。
  3. AI agent 在 monorepo 里迷路。仓库一大,agent 找文件靠猜。修法:在根目录 AGENTS.md 里写「包地图」:每个包一句话说明 + 什么时候该改它。这是本轮《AGENTS.md 项目手册》指南的直接应用。
  4. 把 secrets 放进共享包。packages/config 里放了带 key 的配置文件,所有 app 都能读到。修法:配置包只放结构,secret 永远走环境变量(呼应 g1 的 secret 管理)。

上线前 checklist

  • ☐ pnpm install 在干净环境一次通过,lockfile 已提交
  • ☐ 根目录 turbo run build 全绿,第二次跑命中缓存(几秒内)
  • ☐ turbo run typecheck 覆盖所有包,无错误
  • ☐ 改 packages/db 字段后,api 和 web 的 typecheck 都报错(验证联动有效)
  • ☐ Vercel Root Directory / build filter 已配,预览部署成功
  • ☐ Dockerfile 在仓库根目录,--filter 只装需要的包
  • ☐ CI 用 ...[HEAD^],只改 web 时 CI 不跑 api 的构建
  • ☐ AGENTS.md 里有包地图,AI agent 能找对地方

总结一句话:monorepo 不是「把代码放一起」,是把「跨包一致性」从人的记忆力外包给工具链——tsc、turbo 的依赖图、workspace 引用。对一人一 AI 的团队,这是用结构换脑力的划算买卖。

阅读 0评论 0

评论 (0)

ME
0/1000
评论加载中...
浏览项目广场发布你的项目

相关文章

办公桌上摊开的纸质账单与笔记本电脑,象征 PDF 成品交付主题
指南
别让客户收到乱码账单:vibe 项目的 PDF/报表导出实战

账单发到客户手里却变成乱码?本篇教你用 Puppeteer Page.pdf 做中文排版、pdf-lib 做合并盖章,覆盖字体子集化、表格跨页、页眉页码、大文件分片生成与沙箱安全,一次搞定 vibe 项目的 PDF 成品交付。

Node.js部署上线后端工程
实拍:伏案写作的人,笔记本旁放着电脑
指南
用户写了 2000 字然后丢了:vibe 项目的草稿自动保存实战

用户写的 2000 字,要扛住浏览器崩溃、断网和误关标签页。三层防线——输入防抖、localStorage 兜底、带版本号的服务端持久化,外加版本号乐观锁的冲突 UX(保留两份让用户选)、离线重放与草稿生命周期。

项目创作设计体验独立开发