1994年,父亲带回一台Amiga 500。我不懂什么基准测试,只知道磁盘转太久就是有问题。没有正式指标,只有有限的耐心和眼前具体的麻烦。三十年后,我发布了一份pnpm vs npm vs yarn的基准测试报告,数字漂亮:安装时间、磁盘占用、冷缓存与热缓存对比。工整,可发表,却对即将发生的事完全失明。
因为那份测试只测量了安装环节。它没测当pnpm workspaces遇上Next.js 16 App Router,在缓存不完整的CI环境里、在跨workspace共享包的场景下会发生什么。本地bash脚本计时看不到这些。这些东西只在Railway流水线晚上11点抛出晦涩报错、构建跑了18分钟还没结束时才会现身。
![]()
我的核心判断:pnpm workspaces在2026年仍是monorepo最优选,但存在一些边缘案例——它们不会出现在任何安装时间基准测试里,却可能让你在CI中耗费数小时调试,如果你不清楚Next.js 16 App Router需要哪些具体配置。这不是pnpm的bug,而是其严格隔离模型的文档化后果,正是这一模型让它在其他方面表现卓越。问题在于官方文档假设你已读完所有前置上下文,而在CI环境中这个假设不成立。
![]()
基准测试遗漏的问题:缓存失效与工作区提升(hoisting)
原始测试结构很简单:一个monorepo,两个应用,一个共享包。脚本测量了从零开始和带缓存的pnpm install。数字好看。但我没测pnpm在CI中以下组合条件下的行为:
— 共享包@repo/ui,内含React组件
— 应用apps/web,使用Next.js 16 App Router,从@repo/ui导入
— GitHub Actions缓存~/.pnpm-store跨运行保留
— Railway作为部署目标,自带构建步骤
这种场景下的错误不出在pnpm install,而出在Next.js构建阶段,报错信息足够模糊,足以让你找错方向:
Error: Cannot find module '@repo/ui/components/Button'
Require stack:
- /app/apps/web/.next/server/chunks/[turbopack]_root_of_the_server__[...].js
这个错误在此上下文中不是导入路径写错。它是pnpm在工作区中处理依赖提升的直接后果——配合嵌套的node_modules结构,以及Next.js 16 Turbopack与webpack截然不同的模块解析方式。
![]()
pnpm的提升机制如何运作(以及为何在此失效)
pnpm官方文档(pnpm.io/workspaces)解释了其模型:与npm和yarn不同,pnpm默认不做激进提升。工作区中每个包在自己的node_modules里拥有独立依赖,共享包通过符号链接连接。这种严格隔离是pnpm磁盘效率和安全优势的基础。
但当Next.js 16 Turbopack在CI容器中解析模块时,它期望某些依赖以特定方式可访问。pnpm的嵌套结构加上部分缓存,导致Turbopack在构建时找不到@repo/ui——尽管安装阶段一切正常。
修复不需要换工具。需要理解pnpm的node-linker配置选项,以及何时在CI中强制使用shamefully-hoist模式,或更精细地配置Turbopack的resolve别名。这些在官方文档里都有,只是散落在不同页面,且假设你同时读过pnpm、Turbopack和Next.js三边文档。
教训:基准测试测量的是你定义的问题。但生产环境的问题往往在你定义的范围之外。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.