恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Vite + pnpm Monorepo 部署到 Vercel 的踩坑指南与配置解析

  • 首页
  • 资讯中心
  • /
  • Vite + pnpm Monorepo 部署到 Vercel 的踩坑指南与配置解析

相关资讯

嵌入式工程师的Obsidian+AI知识管理系统架构与实践 2026/10/8 16:07:08
AMD Ryzen跑VCF 9.0.2:NSX Edge部署与性能调优实战 2026/10/8 16:02:07
python 抽象类的使用详解 2026/10/8 16:02:07

最新资讯

文本编辑快捷键效率指南:从VC6.0到VS2008的TaoToken配置实践
AI Agent Harness Engineering 容量规划与弹性扩展:TaoToken 统一 Key 通道下的并发压测与自动扩缩容配置
【收藏必备】ChatGPT拥抱MCP:一条Prompt实现全自动化,小白也能轻松上手|TaoToken统一Key接入实战
【学习笔记】框架层坍缩——LangChain 们正在被重新定义-14/15:从 LangGraph 到 Harness,Agent 编排的下一站在哪
【Bug已解决】OpenClaw 报错 plugin load failed: dependency tree corrupted 解决方案:用 TaoToken 统一 Key 通道排查依赖树
JavaEE二手书交易系统:Servlet+JDBC完整电商闭环实现

今日推荐

context-mode实战指南:从全量塞入到结构化裁剪与检索增强
大模型对话上下文管理实战:三种模式与Token优化
抖音用户主页视频数据爬虫详解:点赞、收藏、分享字段抓取与 TaoToken 统一 Key 配置

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Vite + pnpm Monorepo 部署到 Vercel 的踩坑指南与配置解析

发布时间:2026/10/8 16:07:08
Vite + pnpm Monorepo 部署到 Vercel 的踩坑指南与配置解析 踩了两天坑终于把一个用 Vite 构建的 pnpm Monorepo 项目部署到 Vercel。起初我以为只需要在仪表盘里把构建命令改成那个子应用的命令结果发现事情远没有这么简单。Root Directory、输出目录、环境变量、共享包变更、路由 rewrite、构建缓存一层一层挖下去才真正搞懂 Vercel 和 Monorepo 之间是怎么配合的。这篇文章我按自己的排坑顺序来写先讲整体设计思路再给配置最后把每个坑的原理和解决方式拆开说。无论你是刚把项目改成 monorepo还是已经部署但时不时冒出诡异问题这篇应该能帮你省下不少时间。1. 整体设计与思路拆解1.1 为什么是 Vite Monorepo很多团队一开始不是刻意选择 Monorepo而是项目拆包拆着拆着就自然长成了这种结构。我这边的情况是一个主应用和一个后台应用它们共用 UI 组件、请求封装、通用类型和工具函数。早期把这些公共代码直接复制到各自仓库里每次改个组件要改两遍偶尔还会改漏。后来决定抽成独立的包用 workspace 把它们组织在一个仓库内。选 Vite 而不是 Webpack是因为开发阶段的热更新体验差距太大了。几个包加在一起Vite 冷启动基本在 1-2 秒esbuild 预处理依赖之后改动代码的反馈几乎是实时的。这对每天高频改组件的场景是刚需。但 Monorepo Vite 的爽点主要集中在开发体验到了生产构建和部署环节就轮到“架构债”还利息了。Vite 默认针对单包应用构建产物的输出路径、依赖解析、环境变量作用域全都是围绕“当前项目目录”设计的。当这个“当前目录”变成仓库根目录时很多默认行为都会变。所以我们部署的第一性原理就是明确定义 Vercel 的“项目根”和 Vite 的“应用根”并让两者之间的所有路径显式化不要靠隐式推断。1.2 Vercel 在 Monorepo 部署中的定位Vercel 本质是一个“把 Git 仓库拉下来跑 install build然后把产物托管到全球 CDN”的托管平台。它对单包项目的零配置体验做得非常好检测到 Vite 会自动安装依赖、跑 build、输出 dist。但在 Monorepo 里它无法自动判断该构建哪个子应用因为一个仓库可能有多个可部署的前端应用。为此 Vercel 引入了 Project 和 Root Directory 的概念。Project 是一个虚拟的部署单元Root Directory 是项目在仓库中的根目录。每个 Project 只能配置一个 Root Directory。这样你可以为仓库中的apps/web、apps/admin分别创建两个 Project它们共享同一个 Git 仓库但各自维护自己的构建配置、环境变量和部署域名。这种模型的优点是灵活缺点是隐藏了很多 Monorepo 的联动逻辑。Vercel 的自动检测只能解决“目录内”的事情无法解决“跨目录依赖”的事情。比如apps/web依赖packages/ui当packages/ui的代码变化时Vercel 并不知道这两个目录之间存在关系。这就是很多 Monorepo 部署诡异问题的根源平台在目录维度上的理解和你项目在依赖维度上的理解并不一致。看清楚这一点后面遇到的坑基本都是可以预判的。1.3 部署架构与目录规划我最终的仓库布局如下monorepo-root/ ├─ pnpm-workspace.yaml ├─ pnpm-lock.yaml ├─ package.json ├─ vercel.json ├─ apps/ │ ├─ web/ │ │ ├─ src/ │ │ ├─ vite.config.ts │ │ ├─ package.json │ │ └─ tsconfig.json │ └─ admin/ │ ├─ src/ │ ├─ vite.config.ts │ └─ package.json └─ packages/ ├─ ui/ │ ├─ src/ │ └─ package.json └─ shared/ ├─ src/ └─ package.json注意vercel.json放在了仓库根目录这让它成为所有 Project 的默认配置。我曾在apps/web下也放了一个vercel.json后来发现如果 Root Directory 不是apps/web那个文件根本不会被读取。这是一个重要经验Vercel 只读取“Project 的 Root Directory”下的vercel.json。在部署时我最初想当然把 Root Directory 设为apps/web因为 Vercel 的文档说“如果是 monorepo请手动指定 root directory”。结果构建时 pnpm 报错说找不到 workspace 协议workspace:*对应的包。原因很直白当 Root Directory 被认定为项目根目录后Vercel 会在该目录下执行安装命令和构建命令它根本不知道上层还有个pnpm-workspace.yaml。所以设计部署架构时一定要想清楚“部署单元”和“仓库根”的关系。我最终采用的方式一眼看上去不太常规却非常稳定把 Root Directory 设为仓库根目录.安装命令用pnpm install构建命令用pnpm --filter web build输出目录写成apps/web/dist。这样 Vercel 视角里整个仓库就是“一个项目”但它打包的其实是 web 子应用。这种做法的缺点是所有文件变动都会触发这个项目部署后面我会用忽略命令做一层兜底。2. 环境准备与工程配置2.1 包管理器选择pnpm workspace 的优势包管理器推荐使用 pnpm。pnpm 的依赖结构是“全局存储 硬链接 符号链接”每个包都只会真实安装一次然后通过 node_modules 的软链来引用。这比 npm 的平铺结构节省空间得多更关键的是它有效避免了幽灵依赖。举个例子packages/ui依赖了lodash但在它自己的 package.json 里没有声明只是某个间接依赖把 lodash hoist 到了根 node_modules。在 npm 下packages/ui可以直接 importlodash因为 node 解析路径时会向上找到根目录的 node_modules。虽然能用但这个依赖是“隐形”的哪天根目录的 node_modules 布局变了就崩。pnpm 下就不能这么玩它保证了每个包只能访问自己显式声明的依赖这对长期维护非常友好。同时Vercel 原生的 pnpm 支持做得不错。你只要在根 package.json 里声明packageManager: pnpm8.15.9Vercel 会自动处理 pnpm 版本。但如果手动指定 install 命令一定要带上--frozen-lockfile避免因为 lockfile 不是最新导致安装出意外。另外pnpm 7 会默认阻止依赖安装阶段执行构建脚本postinstall比如 esbuild、sharp 这类依赖需要额外批准。Vercel 平台默认会做一步pnpm approve-builds之类的处理但如果你自定义安装命令时忘了这一步日志里会看到类似Ignored build scripts: esbuild的提示。这时候需要在pnpm-workspace.yaml里配置onlyBuiltDependencies或执行pnpm approve-builds。2.2 工程结构初始化与 workspace 协议初始化步骤很简单在仓库根目录创建pnpm-workspace.yamlpackages: - apps/* - packages/*根目录package.json设为私有防止仓库根被意外发布到 npm{ name: monorepo-root, private: true, packageManager: pnpm8.15.9, engines: { node: 18 }, scripts: { dev:web: pnpm --filter web dev, build:web: pnpm --filter web build, build:packages: pnpm -r --filter ./packages/** build } }在apps/web/package.json中通过workspace:*引用共享包{ name: web, dependencies: { monorepo/ui: workspace:*, monorepo/shared: workspace:* } }workspace:*让 pnpm 在本地安装时自动链接到packages/ui和packages/shared的实际源码目录。这样做的好处是开发时改动共享包代码上层应用可以立即热更新不需要每次手动重新发布。但代价也在这里如果部署平台没有正确识别 workspace 协议它会在安装依赖时尝试从 npm registry 下载monorepo/ui结果必然 404。所以 Vercel 的 install 命令必须使用 pnpm而且 Root Directory 必须在包含pnpm-workspace.yaml的目录下。2.3 Vite 构建配置要点Vite 的构建配置中核心是root基准和envDir。默认情况下Vite 会把包含配置文件的目录当作 root。当我们在根目录执行pnpm --filter web build时实际进入apps/web后执行vite build所以 root 是apps/web输出目录相对这个 root 计算。vite.config.ts可以参考这样import { fileURLToPath, URL } from node:url import { defineConfig } from vite import react from vitejs/plugin-react export default defineConfig({ plugins: [react()], resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)), monorepo/ui: fileURLToPath(new URL(../../packages/ui/src, import.meta.url)) }, dedupe: [react, react-dom] }, build: { outDir: dist, emptyOutDir: true, sourcemap: true }, envDir: ../../ })这里envDir: ../../是指从apps/web向上两级到仓库根目录让 Vite 也读取根目录下的.env文件。不过这样做有个副作用它会把仓库根目录的所有.env*都拉进来需要你自己看清楚哪些变量是给 web 这个应用用的。更稳妥的做法是仍然在apps/web内管理.envVercel 的注入变量属于进程级环境变量不需要envDir也能读到所以envDir这行可以按需保留。关于 alias 指向共享包源码的路径这取决于共享包的发布形态。如果共享包是用 TypeScript 源码直接对外开发那么 alias 指向源码目录没问题如果共享包是通过tsup编译后的 dist就应该指向packages/ui/dist。指向源码的好处是开发时不需要额外构建共享包且 Tree Shaking 更彻底坏处是 Vite 在打包时会把共享包的源码和 web 的源码一起编译如果两个包用的 tsconfig 不一致可能报错。我这里的做法是开发期 alias 指向源码生产构建时先执行pnpm --filter ui build再让 alias 指向 dist。这样兼顾体验和稳定。3. Vercel 部署配置与关键参数3.1 Root Directory 的两种姿势与坑Root Directory 有两种选择各有坑。第一种Root Directory 设为apps/web。这种情况下Vercel 认为项目的构建上下文是apps/webinstall、build 命令都会在apps/web下执行。如果你的仓库根没有 pnpm-workspace.yaml或者不打算用 workspace 协议这样是最直观的。但一旦共享包存在就必须在 install 命令里手动cd ../..构建命令里用pnpm --filter web build输出目录写dist。看上去简单实际上每次部署都要担心脚本里的相对路径对不对而且 Vercel 对根目录之外的变更不会触发该 Project。一旦有人改了packages/ui这个 Project 不会重新构建这就形成了一个大坑。第二种Root Directory 设为.。整仓库作为一个项目install 和 build 在根目录执行输出目录写apps/web/dist。优点是所有命令都可以在根目录语境下正常工作特别是 pnpm workspace 的--filter能按包名精确选择。缺点也很明显任何子目录的文件变更都会触发这个 Project 的部署导致不必要的构建。如果你在同一个仓库还有apps/admin你需要为它单独再建 Project同样把 Root Directory 设为.但buildCommand改为pnpm --filter admin build。这样两个 Project 都会因为任意文件变更而重新构建会有资源浪费。我在实践中最终选择了第二种然后在 Ignored Build Step 中加了文件变更过滤把“任意文件变更都触发”带来的副作用降到了最低。这种方式在多个应用并存时依然可控因为你只是在每个 Project 的过滤脚本里列出自己关心的路径。3.2 构建命令、安装命令和输出目录组合把配置写到vercel.json的好处是版本可控、改动可 review。我的最终配置如下{ rootDirectory: ., outputDirectory: apps/web/dist, framework: vite, buildCommand: pnpm --filter web build, installCommand: pnpm install --frozen-lockfile, rewrites: [ { source: /(.*), destination: /index.html } ] }这里有个关键outputDirectory和 Vite 的outDir不是一回事。outputDirectory是告诉 Vercel最终托管目录从哪里获取。如果你把outputDirectory写成dist而实际产物在apps/web/distVercel 会报Output directory not found。反过来如果你用 Root Directory 为apps/web的方案那么outputDirectory应该写dist。记不住的话就记一个原则所有路径都是从rootDirectory出发。buildCommand里可以用pnpm --filter web exec vite build也可以直接用pnpm --filter web build后者会读取apps/web/package.json里的 build 脚本。如果 build 脚本里已经包含vite build --mode production就不要重复指定 mode除非你想覆盖某个场景。framework: vite并不是必须的但写上可以省去 Vercel 自动检测的步骤。如果遇到平台误判为 Next.js 或其他框架显式声明能避免不少麻烦。3.3 环境变量与 .env 加载顺序Monorepo 环境变量的问题可以分成两层一是 Vercel 面板上的环境变量是否注入到构建进程二是 Vite 如何把这些变量应用到import.meta.env。Vercel 的环境变量是在构建开始时以 shell 环境变量的形式注入的所以只要在构建命令里能通过process.env读取Vite 也能通过loadEnv读取。但 Vite 默认会根据 mode 加载项目 root 下的.env文件.env、.env.production等。如果你的仓库根目录也有一份.env而 Vite 的 root 是apps/web那么根目录的.env是不会被加载的除非你设置了envDir指向那层目录。这正是很多人在 Monorepo 中配置环境变量后不生效的第一个原因。第二个原因是缓存。Vercel 的构建缓存包含 node_modules、build 缓存等但环境变量本身属于构建上下文不会缓存到产物里。真正的问题往往是你修改了 Vercel 面板环境变量后旧版本里可能缓存了上一次的import.meta.env值由于 Vite 预构建依赖缓存没有失效导致构建时引用了旧值。解决办法和构建缓存一样在构建命令前做一次干净的清理或者强制 Vite 重新执行依赖预构建。第三个原因是变量命名。只有以VITE_前缀开头的变量才会被 Vite 暴露到客户端。如果你在 Vercel 面板里设置了一个叫API_BASE的变量前端代码是拿不到的只能在服务端构建流程中使用。想在前端用必须命名为VITE_API_BASE。这是一个很低级但很常见的坑。4. 踩坑实录与底层原理4.1 依赖符号链接引起的幽灵依赖问题pnpm 的 node_modules 结构不是平铺文件而是一堆符号链接。在 Vercel 的 Linux 构建环境中pnpm 的符号链接通常能正常存在但在最终上传产物时Vercel 只会上传outputDirectory里的静态文件node_modules 压根不在发布范围。所以符号链接想象中的“部署后找不到路径”不会发生。真正的问题是构建阶段。Vite 在解析依赖时默认会扫描 node_modules 中的符号链接。当应用依赖monorepo/ui而 ui 内部又引用了 reactVite 会尝试分析依赖图。如果没有resolve.dedupeVite 可能把 react 解析成packages/ui/node_modules/react而 web 自身引用的 react 在apps/web/node_modules/react。两个不同路径的 react 会被视为两个模块导致 React 内部状态错乱。典型症状是页面能渲染但切换路由后状态不共享或者使用 context 时出现异常。排查方法是使用vite build --debug看看模块图中 react 的路径。解决方式是resolve.dedupe: [react, react-dom]告诉 Vite 强制将这些模块解析到单一位置。另一个符号链接相关的问题是 Node 的模块解析。有些构建工具会启用resolve.preserveSymlinks这会改变 node 的默认解析行为让它不再顺着 symlink 去解析真实路径从而绕开一些重复安装问题。但 Vite 默认关闭这个选项保持 Node 的默认行为。所以如果你在 Vercel 上同时使用其他框架而构建链中涉及符号链接务必要弄明白各个工具对 symlink 的处理策略否则会出现本地和线上行为不一致。4.2 构建缓存引发的更新不生效Vercel 的构建缓存机制很激进它是为了加快迭代速度。每个部署任务如果检测到 lockfile 没有变化、安装步骤可以复用镜像就会直接跳过 install 阶段并复用上一份 node_modules。同时它也会缓存一些框架的构建产物比如.next/cache、node_modules/.vite等。这原本是好事但对 Monorepo 却是陷阱。我一共遇到过两种缓存坑。第一种是依赖预构建缓存改了共享包的依赖关系或者共享包里新增了导出但 Vite 的预构建缓存没有失效导致打包时仍然用了旧的依赖图。表现是构建日志显示成功但新页面里找不到新增的导出。解决方法是给 Vite 构建命令加--force或者清理node_modules/.vite目录。第二种是产物缓存Vercel 在检测到输出目录下文件未变化时可能复用上一次的产物但这种情况比较少见更多是依赖安装问题。更麻烦的是缓存 key 对 Vercel 来说是仓库整体快照但在 Monorepo 中各应用共享同一个快照。如果你有两个应用在一个仓库里A 应用改了代码触发了 A 的构建B 应用没触发但 B 的下一次构建时Vercel 的缓存系统可能把 A 产生的中间产物也缓存进去导致 B 的构建环境混入了 A 的残留。这种问题很难复现我建议在 Monorepo 场景下构建命令尽量保持“短小、确定”不依赖跨包的中间产物需要共享的东西显式构建并放在明确的目录。4.3 SPA 路由刷新与 rewrite 原理SPA 路由的 404 问题不是 Vercel 独有任何静态托管都会遇到。原理是服务器收到/about请求后按文件系统找about.html发现不存在就返回 404。而 SPA 的约定是所有深层路由都应由index.html接管由前端路由器决定渲染什么页面。所以需要在托管层做一次“所有路径重写到 /index.html”。vercel.json的rewrites数组里source支持路径匹配destination是最终响应文件。写成/(.*)表示匹配所有路径/index.html是目标。注意rewrites不会改变 URL浏览器地址栏依然是/about返回内容却是index.html。这是实现 SPA 刷新不 404 的标准方法。我见过有人用redirects写得不对造成循环重定向原因是把重定向目标写成了网站根路径而不是文件路径。记住rewrites优先于redirects且 source 是相对站点根路径的匹配不是文件系统路径。还有一个容易忽视的细节如果 Vite 配置了base: /admin/那么应用部署在/admin子路径下rewrite 的 source 要相应改成/admin/(.*)destination/admin/index.html。否则直接访问子路径下的深层路由也会 404。4.4 共享包改动如何触发重新部署这是 Monorepo 部署中最疼的一个点。Vercel 的触发机制是根据 Project 的设置决定哪些 Git 文件变化会触发构建。默认情况下如果 Project 的 Root Directory 设置为apps/web那么packages/ui下的变化不会触发这个 Project 的构建。从触发角度Root Directory 为.的方案最直接任何文件变化都会触发构建但会造成多个 Project 一起构建。所以我在 Ignored Build Step 中设置了一个脚本比如if git diff --name-only HEAD~1 HEAD | grep -qE ^(apps/web|packages/ui)/; then # 有相关变更返回非零让 Vercel 继续构建 exit 1 else # 没有相关变更跳过构建 exit 0 fi这个脚本的逻辑是拿出最近一次提交涉及的文件列表筛选出apps/web或packages/ui开头的路径。如果命中则继续构建否则跳过。需要特别提醒不同时期的 Vercel 对构建跳过退出码的定义不完全一样网上很多教程互有出入最靠谱的做法是在自己的账号里实测一次跑一个无关文件的提交看看到底是跳过还是继续。把脚本的语义确认清楚后这个方案就能稳定工作了。4.5 Vercel Labs scriptc 与缓存破局最近社区里聊 Vercel Labs 的 scriptc 比较多。我理解 scriptc 是 Vercel 对构建脚本执行结果做缓存的一种优化目的是在相同配置、相同源文件的情况下跳过部分可重复执行的脚本缩短构建时间。它的原理和 Vercel 构建缓存类似都基于内容 hash 和快照比较。但在 Monorepo 环境下scriptc 很可能把“安装依赖”和“构建共享包”这两个环节过度缓存你的packages/ui代码变了但共享包在 package.json 中的版本号没变脚本执行结果被判定为“无变化”于是继续使用旧的产物。对付这种问题最简单的破局手段是给构建命令注入一个随提交变化的变量比如FORCE_REBUILD$(git rev-parse --short HEAD) pnpm --filter web build这样每次提交的 hash 不同环境变量不同脚本缓存无法命中就会强制重建。代价是每次都全量执行牺牲了构建速度。如果你信任缓存但想让它失效得更合理更好的做法是给共享包设置独立的版本号并在 web 的 package.json 里用明确的版本依赖而不是workspace:*。当然这会失去开发时自动化连接需要配合 release 流程。两种方式各有取舍小项目不需要折腾大项目再考虑。5. 常见问题排查与性能优化5.1 部署失败日志速查表为了方便排查整理一个速查表覆盖最常遇到的几种错误表现。症状可能原因重点检查安装阶段报ERR_PNPM_WORKSPACE_PACKAGEpnpm 未在包含 workspace 的根目录执行Root Directory 是否为.install 命令是否显式使用 pnpm构建时提示Could not resolve monorepo/uialias 配置错误或 workspace 包未链接检查 pnpm-workspace.yaml、web package.json 的 workspace 协议、vite alias 路径构建后找不到输出目录outputDirectory与实际产物路径不一致构建日志最后几行看 Vite 输出的路径页面能打开但刷新 404没有配置 SPA rewrite确认 vercel.json 位置和 rewrites 规则环境变量值为 undefined变量名缺少VITE_前缀或 envDir 未配置设置 VITE_ 前缀确认 envDir 指向 root代码改了但线上没变化构建缓存 / 依赖预构建缓存清理.vite或使用--force重新构建每一类问题我都在前面详细讲过这里主要是用来快速定位。拿到日志后先看是哪个阶段失败install 阶段失败先看包管理器build 阶段失败先看输出目录upload 阶段失败先看文件大小和路径。5.2 本地正常线上失败的原因“本地跑得好好的一上 Vercel 就挂”是最高频的求助。除了环境变量和依赖管理差异还有几个 Vite Monorepo 特有的原因。第一个是 Vite 的root推断差异。本地你在apps/web目录直接vite buildroot 是apps/web在 Vercel 上如果你用根目录执行pnpm --filter web build实际上 Vite 依然会把apps/web作为 root因为pnpm --filter web build会进入包目录再执行 npm script。如果构建命令写成了pnpm --filter web exec vite build而当前工作目录是根目录那么 Vite 会认为 root 是根目录此时outDir、envDir全部乱套。所以最好统一用包的 script避免用exec。第二个是 Node 版本。Vercel 默认 Node 版本可能和你本地不一致而 Vite 5 要求 Node 18。如果你的package.json没有engines也没在 Vercel 面板指定 Node 版本旧版本可能直接语法报错。建议在根package.json中加上engines: { node: 18.0.0 }并在 Vercel 的 Settings - Node.js Version 里选择对应的 20.x。第三个是 TS 和构建工具的依赖关系。Monorepo 里如果共享包是 TypeScript 写的而共享包没有构建产物web 构建时需要让 Vite 能正确处理 TS 文件。直接 alias 到源码时Vite 会使用 esbuild 转译 TS但如果你共享包的 tsconfig 里指定了declaration或路径别名可能会引入解析问题。最稳妥的方式是共享包用tsup或 Vite 的 library mode 提前构建产出 JS 和 d.ts再让 web 引用产物。这会多一步构建但省去了很多奇奇怪怪的 TS 解析问题。5.3 构建提速与缓存策略Monorepo 的构建提速核心是减少“重复构建”和“重复安装”。Vercel 已经帮我们把 install 缓存做得不错只要 lockfile 不变install 基本秒过。我们自己能优化的点是共享包的预构建和 web 的按需构建。我采取的策略是共享包用tsup进行独立构建把它编译成 ESM CJS 双格式产物然后 web 的 alias 指向 dist。这样 Vercel 构建 web 时Vite 不需要去编译 TypeScript 源码也不需要把共享包源码纳入 watch 范围构建速度会明显加快。构建命令可以写成一条链pnpm --filter ui build pnpm --filter shared build pnpm --filter web build前面的产物作为后面的依赖。为了让缓存更精确如果共享包源码没变pnpm --filter ui build会很快web 构建也能复用 Vite 的缓存。为了保险还是建议把.vite缓存单独处理或者由 Vercel 缓存node_modules/.vite。另一个提速点是 Vite 的build.target。如果目标浏览器较新可以把build.target: es2020这能让 Vite 做更少的转译减少代码体积和构建时间。还有sourcemap生产环境如果不需要可以关掉构建和上传都会快很多。当然排查问题时就另当别论。5.4 实战建议与最终配置样例最后给出一套我实测稳定的配置可以直接参考。仓库根vercel.json{ rootDirectory: ., outputDirectory: apps/web/dist, buildCommand: pnpm --filter web build, installCommand: pnpm install --frozen-lockfile, framework: vite, rewrites: [ { source: /(.*), destination: /index.html } ] }根package.json重点字段{ name: monorepo-root, private: true, packageManager: pnpm8.15.9, engines: { node: 18 }, scripts: { build:packages: pnpm --filter ui build pnpm --filter shared build, build:web: pnpm --filter web build } }apps/web/package.json里的 build 脚本{ scripts: { build: vite build --mode production } }如果你的 web 依赖共享包的 dist 产物那么构建命令改为pnpm --filter ui build pnpm --filter web build。如果共享包代码不经常变且 Vercel 缓存没问题可以只构建 web。环境变量确认前缀是VITE_然后不需要额外配置 envDir因为 Vercel 注入的是进程环境变量。Ignored Build Step 脚本也可以按需加上。不过我在实际项目里发现如果 Root Directory 是.共享包改动会触发构建这其实也符合预期只是多构建几个无关应用。如果你的项目数量多再考虑用脚本过滤。最后再分享一个我自己的习惯每做一个新的 Monorepo 工程我都会在首次部署时用一个空 commit 触发部署观察完整日志确认 install、build 和 upload 三条链路都正常后再开始写业务代码。因为部署环境的怪问题往往藏在最开始时越早暴露越好排查。遇到莫名其妙的线上问题先别急着改代码打开 Vercel 的构建日志往前翻十行看有没有cache skipped或者scriptc的关键提示这能省很多时间。Monorepo 的部署本质就是和“路径”以及“缓存”两个东西较劲把这两个理清楚了剩下的都是重复劳动。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号