5 个应用装进一个仓库 - 独立开发者的 Turborepo Monorepo
在一个仓库里管理多个应用和网站的 Turborepo Monorepo 搭建方法。共享公共配置、靠缓存省下构建时间、按应用分别部署,全部从实战视角整理。
核心摘要
一个人做多个应用,配置就会散落各处。用 Turborepo Monorepo 把 eslint 和 tsconfig 收进 packages 共享,turbo 缓存让没变的应用不再重复构建,--filter 实现按应用部署。
目录
一个人做应用,仓库会哗哗地涨。一个 App,一个网站,又一个 App… 然后 eslint 配置被你复制粘贴了五遍。我把这些全并进了一个 Monorepo。
散落的配置才是真问题
仓库一多,最疼的不是代码,是配置。
- eslint、prettier、tsconfig 每个仓库复制一份
- 改一条规则,就得挨个仓库巡回修改
- 公共 UI 和工具函数,发到 npm 又嫌太琐碎
Monorepo 把这一切变成在一个地方共享。
基本结构
apps/
├── web/ # 랜딩
├── blog/ # 이 블로그
├── wave/ # 오늘의 파도
└── ...
packages/
├── eslint-config/ # 공용 eslint
└── typescript-config/ # 공용 tsconfig
apps/* 是实际的部署产物,packages/* 是各应用共享的配置和代码。每个应用的 tsconfig.json 只做继承。
{ "extends": "@repo/typescript-config/nextjs.json" }
从此规则只在 packages 一个地方改。
turbo 缓存是核心
Turborepo 真正的武器是缓存。只要任务的输入(源码、配置)没变,它就直接复用上一次的结果。
turbo run build # 전체 빌드(바뀐 것만 실제로 돎)
turbo run build --filter=blog # blog 앱만
在 turbo.json 里声明任务间的依赖和输出就够了。
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": [".next/**"] }
}
}
从第二次构建起,就是 "FULL TURBO",几秒钟收工。
按应用分别部署
虽然是一个仓库,部署仍然按应用分开。用 Docker 的话,turbo prune 可以只切出对应应用的子树,让镜像保持轻量。
turbo prune blog --docker # blog + 내부 의존성만 남긴 out/ 생성
这个博客就是这样切出来,用 Docker 部署到迷你主机上的。那部分故事在 自托管那篇文章 里。
小结
apps/*= 部署产物,packages/*= 共享配置- tsconfig 和 eslint 靠继承,只在一处维护
turbo缓存让没变的应用不再重复构建- 用
--filter和prune实现按应用部署
如果你一个人运营着多个产品,Monorepo 不是奢侈品,而是生存工具。
常见问题
有几个应用时上 Monorepo 才划算?
只要超过 2 个、出现了公共配置(eslint、tsconfig、UI),就已经划算了。核心在于配置只改一处,所有应用同步生效。
构建不会变慢吗?
反而更快。turbo 会缓存输入没有变化的任务结果,只重新构建发生变化的应用。
相关文章
- 💻 开发
一个桶有两个主人时,最后 apply 的一方获胜
我加了一条 S3 lifecycle 规则,terraform apply 却失败了。原因是同一个桶的 lifecycle 配置被两个资源各自拥有。S3 lifecycle 不是按规则粒度,而是按整个文档粒度生效,最后的赢家会悄悄抹掉对方的规则。
- 💻 开发
测试一片绿,通知却从来没发出去过一次
往队列里塞任务的代码从上线第一天起就每次都失败。错误被吞掉了,测试靠 mock 一直是绿的。一个冒号悄悄干掉三个功能的故事。
- 💻 开发
收到了数据库挂了的告警。可数据库从来没挂过
一天之内堆了四条 CRITICAL 告警。504、Prisma P2028、后台 500,还有一条“DATABASE 服务宕机”。我先打开 RDS 指标,结果那 21 小时里 CPU 峰值只有 19.7%,好得很。慢的不是数据库,而是要横跨大西洋二十五次的往返;而那条“宕机”告警,是健康检查自己造出来的。