2026年,為什麼 NestJS + Monorepo 越來越流行了 ❓❓❓

> 大家好 👋,我是 Moment,目前正在使用 Next.js、NestJS、LangChain 開發 [DocFlow](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fxun082%2FDocFlow)。這是一個面向 AI 場景的協作文檔平台,整合了基於 `Tiptap` 的富文字編輯、`NestJS` 後端服務、即時協作與智慧化工作流程等核心模組。

在這個專案的持續打磨過程中,我累積了不少實戰經驗,不只是 Tiptap 的深度客製、編輯器效能優化和協作方案設計,也包括前端工程化建設、React 原始碼理解以及複雜專案架構實務。

如果你對 AI 全端開發、文件編輯器、前端工程化或者 React 原始碼相關內容感興趣,歡迎加我的微信 yunmz777 一起交流。覺得專案還不錯的話,也歡迎給 DocFlow 點個 star ⭐

image.png

這兩年和別人聊下來,有個挺樸素的觀察:工具都差不多,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。如下圖所示:

Monorepo 目錄示意(含 NestJS 後端與 Next 前端)

樹一展開,比在文字裡憑空想像直觀得多。

我以前在多倉庫裡改過一個 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 腳本,少扯等你發包我先對齊版本的皮。以前在多個倉庫裡的流程,很多變成同一倉庫裡自己重構。

前端寫了多年 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。寫得多漂亮不敢說,只希望一群人加機器一起改的時候,爛得慢一點。


原文出處:https://juejin.cn/post/7636703811729637395


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝12   💬4   ❤️1
464
🥈
alicec
📝1   ❤️2
87
#4
我愛JS
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登