2026年,为什么NestJS + Monorepo越来越流行了 ❓❓❓

大家好 👋,我是 Moment,目前正在使用 Next.js、NestJS、LangChain 开发 DocFlow。这是一个面向 AI 场景的协同文档平台,集成了基于 Tiptap 的富文本编辑、NestJS 后端服务、实时协作与智能化工作流等核心模块。

在这个项目的持续打磨过程中,我积累了不少实战经验,不只是 Tiptap 的深度定制、编辑器性能优化和协同方案设计,也包括前端工程化建设、React 源码理解以及复杂项目架构实践。

如果你对 AI 全栈开发、文档编辑器、前端工程化或者 React 源码相关内容感兴趣,欢迎添加我的微信 yunmz777 一起交流。觉得项目还不错的话,也欢迎给 DocFlow 点个 star ⭐

这两年和别人聊下来,有个挺朴素的观察:工具都差不多,Cursor、Claude Code、Copilot 换来换去,有人照样顺滑往前推,有人却被 AI 拖进更深的坑里。倒不一定是模型突然变差了,更像是仓库本身经不起这么快地改------你一提速,漏的地方也跟得上提速。

我这边遇到过无数次那种很无聊的返工。后端字段改了,前端忘了跟。或者看起来类型都对,实际请求体还是对不上。编译绿灯,线上才发现分支走错。一出问题就先怀疑 prompt,改了两轮发现不对劲------常常是仓库里没有一套固定的摆放方式,模型猜这一步猜对了,下一步就和别处打架。

所以到了 2026 年,我反而更多把 NestJS 和 Monorepo 当作默认选项,不是因为它们听起来高级,单纯是省事:目录大致怎么长、模块怎么切、前后端能不能共用同一份类型说明,至少有个大家都认的底子。AI 跟着改文件的时候,不至于今天一套写法、明天换一套,你自己回看也少猜谜。

以前挑框架会问写着爽不爽。现在会先想过两个月再来需求,我还能不能一眼看出该动哪几块。NestJS + Monorepo 谈不上惊艳,只是让我觉得没那么容易失控。

写出来的快,后面收拾慢

现在问 AI 顺手写一段,在圈里早不新鲜了。身边人多少都会用用 CursorCopilot 一类,写 TS、改多文件的仓库,编辑器也更好跟一点。

省时间的是样板、CRUD、第一遍类型、顺带出来的测试草图。多文件改、读完再改、跑完再交 diff,大家也都摸熟了。网上还有一大把规则文件和模版,抄一抄就能开张。

麻烦的是它仍然吃你仓库长什么样。上下文一碎,就只能对着当前文件蒙,旧接口的臭毛病还能被带回来。约定没写进结构里,同一天里 ValidationPipe、手写 if、跳过注入直接 new 能并存。跨包改一半留一半、临上线才逐行对 diff,都常见。有人习惯 AI 打一版自己再改,省下的时间往往又赔在契约和安全上。

把这些和日常开发叠在一起看,AI 写代码早就不算新闻。起接口、跑 CRUD、补两层类型、顺带生成点测试,交给模型去做,往往不慢,第一眼看上去也像那么回事。别扭的是后半程:很多时候它不是写出 0 分,而是那种能跑、像样、却不对劲的 80 分------lint 不吵,预览也能点开,但分层含糊、命名各写各的、同一个概念在不同文件里换了三张脸。你要是真顺着往下叠需求,常常要到第二、第三次改动才猛然醒悟,省下来的时间没花在第一版上,全花在给前面的草率擦屁股上。

后面这几类我最熟:改一个字段,前后端各漏一处;鉴权相关的判断补丁似的散落在好几个文件里;新开的功能完全是另一套文件夹脾气;类型检查安安静静,DTO、落库和前端调用却已经各走各路。偶尔也会嘀咕,这算不算真省力。

我以前也会比谁敲得快、谁能更快翻出文档。现在更在乎仓库省不省返工,少折腾比好看重要。上下文窗口再大,翻起来顺不顺还是看你自己怎么摆文件夹。

好几个仓库并排的时候

很长一段时间里,我都觉得多 repo 很正常:前端一个仓,后端一个仓,再加共享类型包、组件库,听起来分工清清楚楚。

真到了天天开工、AI 也跟着一起改的时候,摩擦就出来了------业务明明是一套东西,代码却被切成几块互不接壤的地盘,没有哪个仓库能单独回答这一整块系统在干什么。人还能靠记忆和聊天记录勉强对齐,模型手里往往只有当前文件附近那点片段,它没有你那套我懂的脑内地图。

后果都很具体:字段名对不齐,import 指到老路径,接口说明还停留在上个版本,这边改了那边没人提醒,前后端各讲各的故事。于是就经常出现那种撕裂:嘴上都说 AI 很强,手头却在骂它不靠谱;细看往往不是模型突然变笨,而是你根本没给它看过全貌,它只能瞎蒙。

Monorepo 对我来说最实在的一条,就是相关代码至少在一个 workspace 里,搜得到、跳转不瞎跳,改一处牵动谁早一点露馅。

单 workspace 那点实在的好处

大家聊 Monorepo,常常一上来就是依赖 hoist、构建缓存、CI 提速、版本对齐------这些都实打实地省钱省时间。若你用的是 Turborepo、Nx 这类任务编排,改 libs/types 再触达 apps/web 时,turbo run build --filter=... 一类命令往往只跑受影响的那几条边,CI 和本地反馈都轻一些;AI 一口气动多个包的时候,也不太容易因为全量 build 太慢把思路打断。但我日常感触更深的,反而是更土的几件事:全局搜索能跨过 apps 和 libs,跳转定义不会再跳到另一个克隆仓库;开一个合并请求可以同时改 apps/api、前端调用处和 libs/types,评审的人也不用先在脑子里拼接三四份改动。

产业报告里偶尔也能看到 Monorepo 与更高采纳率、更少来回改放在一块儿的讨论,口径各自不同,我不打算在这里背具体百分比。我自己觉得更实在的一点是,同一套索引里改契约,少了很多跨仓漏改。

一种常见的摆放方式大概是这样(命名随团队习惯变,道理差不多):

  • apps/web
  • apps/api
  • apps/worker
  • libs/types
  • libs/db
  • libs/auth
  • libs/ui
  • libs/common

我手里在跑的一个仓库用的也是同一套思路,只是 app 名叫 apps/backendapps/frontend,后端在 src 下拆 apischematypes 等,根上还有 Turborepo 缓存和一份给助手看的 AGENTS.md。如下图所示:

树一展开,比在文字里凭空想象直观得多。

我以前在多仓库里改过一个 shared type,心里会一直挂着还有没有哪个仓库没 bump;现在在同一个 workspace 里,至少引用关系摊开在同一套工具链底下,TypeScript 或单元测试常常会比人肉更早喊疼------哪里还在用旧字段,哪里页面还在按老形状解构,grep 一下也有谱。

再比如后端改了接口返回字段,前端哪些 hooks、哪些组件真正吃到这一次响应,不必全靠记忆里上次好像聊过。这不是什么玄学体验,就是改动触发的影响范围更容易被看见、被追责到同一次合并请求里。

要做 AI 相关的增量也同理:Embedding、RAG、异步任务到底落在 libs/ai 还是单独 apps/worker,一开始就需要个说得过去的落点,不然半年后全是 import 魔法和临时脚本。Monorepo 不提供正确答案,但它逼你把这一坨归谁管迟早说清楚。

在这套习惯里待久了,工作状态会从我在维护好几个小项目悄悄换成我在推进同一个系统。不是口号,是你真的少了很多切仓库、对版本、猜依赖的上下文切换。

单仓也救不了后端胡写

所有代码塞一个仓库,只解决找得到文件,不解决你在 apps/api 里照样把 controller、service、库表访问、杂七杂八工具揉一团。AI 一次改五个文件,耦合只会涨得更快。

我后来还是上了 Nest,图的是入口、业务、横切几件事在目录上有固定叫法,新人进来知道往哪翻,补丁也能长得差不多。它不算最轻,我就看半年以后加模块还痛不痛。

Nest 那套烦人的分层

第一次学 Nest,很多人都会嫌它重:Module、Controller、Service、Guard、Pipe、Interceptor,条条框框比 Express、Fastify 裸奔多出一截,脚手架一念心里先咯噔一下。

但我后来承认,那些让我觉得烦的概念,多半正是复杂之后会回来的质问------HTTP 入口到底挂在哪儿,业务逻辑能不能别再黏在路由文件里,鉴权和校验是不是每次都重写一遍,异常最后统一长成什么样,跨模块的能力能不能复用而不是复制粘贴。你可以在项目很小的时候装作没看见,等体积上来,它们会以技术债的形式敲门。

Nest 对我有用的地方,就是它催你把那些事摊开:Controller 薄一点,Service 扛事,DTO 把进出的形状说清楚,GuardPipeInterceptor 各管一截横切逻辑。写得丑归丑,至少在一条路上。

后端也不可能接口跑亮就结案,需求和权限还来。框架不写业务,只少几次从口头上重新约分层。

装饰器看多了,反而不容易乱窜

我以前当装饰器和 DI 是口味问题,现在要带着助手一起看代码,utils.ts 堆一切最头疼。Nest 那点样板至少是固定格式:@Controller 像关口,@Injectable() 多半进构造函数,Moduleimportsproviders 能看出依赖往哪边走。错误还会犯,多数是接错一层,不至于每个文件一种新的脾气。

构造函数里写字段比一层层 ../../../../ 好跟,对人类和编辑器都一样。

我不再纠结算不算魔法,只在乎新来的、审稿的、还有自动补全,是不是在同一个习惯里读这套目录。

生成越快,烂摊子越容易铺开

听上去怪,能力强了本应少管。我这边反正是反过来的,一次多出好几个文件,结构松的话脏东西也一起铺开。同样一个模型,在规矩紧的 Nest + Monorepo 里多半是补边角,在老脚本堆里经常是 import 散了、校验抄三遍、servicecontroller 又掰扯不清。

选型我就问两件事,多文件改完会不会散,下个补丁你能不能猜到哪一层动。Nest 不是唯一答案,只是我默认懒得再赌。

至于 Express、Fastify 裸着写,我见过太多靠自觉最后靠不住。轻量栈写小服务爽快,HonoElysia 我都用,业务一长我还是想有一层大家都认的摆放。AdonisFoalTS 也行,模版和社区我这儿常碰到的是 Nest。

前后端接缝那档子事

语法、SQL、状态码啃得动,烦的是两半各搞各的目录、README、环境变量,改需求前先在心里对一遍口头合同,明明一个东西却干出两份工的感觉。

Nest + Monorepo 不能砍掉后端工作量,只是把缝抹窄一点。

同一个 workspace 改 API 和页面,共享类型和同一条 linttsconfig 脚本,少扯等你发包我先对齐版本的皮。以前在多个仓库里的流程,很多变成同一仓库里自己 refactor。

前端写了多年 TS,后端再随便 any 心就裂着。契约放在 libs/types 或用生成出来的 SDK 锁住一层,漂移少一桩是一桩。

包管理、CI、分支照旧两套角色,但至少不用每次从零切换脑回路。熟了以后,很难再忍受接口栏两头吵。

若以 Next.js App Router 或类似前端为主力,只是把 Nest 当成好好写业务和善后数据的那一半,这一套目录语言其实不难对齐。路由负责入口像 pageservice 像抽出去的 server libpipeinterceptor 像中间件层。端到端类型上,有人喜欢 tRPCzod 推断加共享 router,有人喜欢 OpenAPI 生成 client。任选一条你能长期维护的主线,把契约锁在 libs/types 或生成的 SDK 里,AI 在前端敲 mutationfetch 时少一半凭空造字段。本地开发里,turbo(或等价物)跑 dev,改 shared 类型后两端热更新的节奏,也常和 AI 快速试错一小步合上拍。部署侧很多平台能对 monorepoapp 建制品,我不再想维护两份各写各的环境变量叙事。

审稿比生成更费工夫

现在大家爱讲几秒出一个功能。我自己的账本里,真正决定是否划算的,常常是后面的半小时到一个小时:目录有没有乱跑,边界有没有偷偷改写,类型和数据是否仍对齐,联动测试要不要补。如果生成省下打字时间,却成倍加到梳理结构上,账就对不上了。

Nest + Monorepo 做的很大一部分省事,是把一大批低级争议前置掉------共享字段在哪儿声明,模块职责默认怎样划,接口改了哪些地方按理应当红光报错。于是评审补丁时我更常在盯业务:权限有没有漏网的路径,异常场景会不会把脏数据写进去,性能热点是不是被忽视了,需求语义到底有没有偏差。

我现在的习惯能多懒就多懒,先跑测试和类型检查,再读业务。让 AI 顺手起一版 VitestJeste2e 骨架并不贵,红线测试挂了就先迭代 prompt。绿了再谈边界条件。@Injectable() 的好处是 mock provider 也相对直来直去,审 diff 的人会轻松一点。

以前看 AI 的补丁,像是在考古这东西为何出现在此;现在更多像是在核对这块业务说得圆不圆。这不是神话 AI,只是把本该机械的对齐成本压低了一层。

我没打算一锅炖成巨石

Monorepo 听上去像要把所有东西糊在一起,Nest 又像老派人做的三层后端。我自己的用法其实很土,源码和好改的契约放在一起,发布照样可以按 app 拆开。

  • apps/web 托管前端
  • apps/api 托管主 HTTP 服务
  • apps/worker 托管队列或异步消费者
  • libs/types 承载共享契约
  • libs/ai 承载模型调用、RAG、prompt 组装之类
  • libs/authlibs/common 分摊认证与通用工具

仓库可以统一规范,制品依然可以按 app 构建发布;你可以先把复杂度关在清晰的包里,而不是一开始假装自己永远只需要一个 server.ts。这在 2026 格外常见------队列、异步生成任务、检索、后台配置、审计日志、多租户开关,后来都会陆续冒出来。

CI 里只对改动的 appturbo run test --filter=...@...(或等价过滤)之类,也早已是常规操作。共享代码动了,顺带跑会消费它的那几个 app,而不是每次全矩阵。托管侧不少平台认得 monorepo 根目录,apps/web 走静态或边缘,apps/api 单独开服务。源码和契约仍在一处捏着,生命周期和扩容却可以拆开看,不必心理上先投降成巨石。

Nest 自带 microservices、传输层那一套,真要把 auth 或大活拆出去,也还是在同一套路子里长枝,不用再拍脑袋起一套新目录癖。

我更在意的是:这些东西加进来的时候,是顺着现有的 libs/apps 生长,还是被迫堆出一层新的临时目录。前者不一定优雅,但至少有机会保持可读;后者常常意味着下一次 AI 生成又会发明一种新秩序。

人一多,文件夹比嘴上规矩管用

一个人单挑项目的时候,坏习惯还能靠记忆兜底;两三个人一起用 AI,风格漂移的速度会快得离谱。某人习惯函数式拼接,某人偏爱大类;有人把逻辑黏在 controller,有人把所有东西都塞进 util;几周下来,目录看起来像百家饭拼盘。

Nest + Monorepo 对团队的价值,不在于消灭分歧,而在于把大量本该口头重复的规矩,换成打开仓库就能看见的骨架------新功能默认落在哪个 app,共享代码朝哪个 lib 收敛,鉴权和 DTO 的习惯写法是什么。AI 这时更像在同一套轨道上补齐缺口,而不是每人拉着模型朝不同方向发明范式。

新人上手也会轻松一点:不必先听完三场口头约定才能下手改第一段代码,结构本身就带着大部分的别这么写。这当然不完美,但比纯粹依赖自律省心。

仓库根上挂一份短短的项目说明(例如 AGENTS.md.cursorrules),往往比喊一百句我们风格是这样管用。仓库本身有条理,助手多半把你的效率往上抬。仓库本来就碎,它也会把那种碎法批量复制出去。条目宁可写得具体一点,也别只剩口号。

下面是一段示意,路径和工具名按你们真实栈改即可:

  • 新功能落在 apps/api/src/<domain>/,按 Nest Module 拆分领域,别把所有业务都摊进同一个大目录。
  • 共享类型与契约收口到 libs/types。DTO 一律配 class-validator,并在引导程序里全局启用 ValidationPipe
  • 鉴权走统一的 Guard(或团队约定的同一套切面),不要在每个 Controller 里各写一版 if
  • 跨包只引用对外公开的边界。优先用包名或 workspace: 协议对齐版本。禁止用一连串 ../../../ 掏进别的 apps/* 内部实现。

Claude Code、Cursor 之类读这类说明时会有点用,再配合仓库里实打实的 Nest 目录,跑偏会少一些。

总结

工具换了几轮,差的大头还是仓库难不难翻。多仓切开以后,光看当前窗口很容易蒙,import、字段名、契约各飘各的。拢进一个 workspace,找和改都短一截,TypeScript 报错和测试红条也常比人肉早。

Monorepo 只管东西在一锅里,治不好后端胡写。我上 Nest,图每层有个约定俗成的叫法,新人也好,编辑器补全也好,少走一点冤枉路。

写那几屏幕往往不费多少钟,时间都耗在审稿、对上类型、补测试。目录利落些,才能多在业务和坑上花功夫。

以后要挂队列、worker,鉴权再想拆出去,也愿意顺着现成的包长枝,不想再养一套谁也不知道的新规矩。

我平常就这么默认:Monorepo 先合上上下文,Nest 把后端层压住,剩下的靠习惯和 CI。写得多漂亮不敢说,只希望一群人加机器一起改的时候,烂得慢一点。

相关推荐
IT果果日记2 小时前
人大金仓使用Flink-CDC
大数据·数据库·后端
前端那点事2 小时前
Vite4.x+打包优化实战指南(无冗余):从体积到速度,一文吃透所有技巧
前端·vue.js
Struggle_zy2 小时前
Vue3 动态路由踩坑记
前端
Gopher_HBo2 小时前
阻塞队列之SynchronousQueue
后端
understandme2 小时前
30 毫秒教会你怎么在 TKE 搭建 Istio
后端
SurgeJS2 小时前
Vue Rex: 一个更简单的 Vue 3 请求库
前端
神奇小汤圆2 小时前
Spring Boot:别再重复造轮子,这些内置功能香麻了
后端
前端那点事2 小时前
Vue十万条数据渲染无卡顿!3种工业级方案(附可复制代码+避坑指南)
前端·vue.js
tonydf2 小时前
快速上手AI网关——LiteLLM
后端·aiops