Windows 上 Next.js `standalone` + pnpm:「构建成功却把 next 删空」的踩坑记录

Windows 上 Next.js standalone + pnpm:一次「构建成功却把 next 删空」的踩坑记录

前言

最近本地开发时经常出现这种循环:

  1. pnpm build 成功
  2. 再跑 pnpm dev 直接炸
  3. 删掉 node_modules 重装才能恢复
  4. 下次再 build,又重来一遍

搞得我每次把node_modules删除重装然后又删了重装,最后忍受不不了的查一下原因解决根本问题

报错通常是:

text 复制代码
Error: Cannot find module 'next/dist/pages/_app'

或更狠一点:

text 复制代码
Error: Cannot find module '.../node_modules/next/dist/bin/next'

第一反应会以为依赖没装好、pnpm 坏了、磁盘坏了。 真正原因更离谱:Next.js 在清理 standalone 产物时,顺着 symlink 把真实的 node_modules 删空了。


现象复现

环境大致如下:

  • OS:Windows
  • 包管理器:pnpm
  • Next.js:14.x(例如 14.1.4
  • next.config 中开启了:
js 复制代码
output: "standalone"

复现步骤:

bash 复制代码
pnpm install
pnpm build   # 成功
pnpm dev     # 失败:找不到 next/dist/pages/_app

或者:

bash 复制代码
pnpm build
pnpm build   # 第二次构建也可能直接把依赖删坏

检查现场会发现一个很反直觉的状态:

  • node_modules/next 这个目录/junction 还在
  • 但里面的文件几乎被删光(文件数可能变成 0

所以不是「模块解析路径错了」,而是 包内容真的没了

这也解释了为什么只能重装 node_modules


standalone 是干什么的?

output: "standalone" 是 Next.js 的生产部署模式。

开启后,next build 会额外生成:

text 复制代码
.next/standalone/

这个目录是一份精简可运行产物 :带上运行所需的最小依赖,适合 Docker 等场景,不必把完整 node_modules 打进镜像。

典型启动方式:

bash 复制代码
node .next/standalone/server.js

对本项目来说:

  • 本地 pnpm dev 不需要 standalone
  • Docker 生产镜像 依赖 standalone(例如用 proxy-server.js 拉起 .next/standalone/server.js

问题就出在:为了生产部署开了 standalone,本地 Windows 上每次 build 也会生成它,随后触发清理逻辑的副作用。


根因分析

1. pnpm 的依赖结构依赖链接

pnpm 不会像 npm 那样把包完整复制到扁平 node_modules,而是通过:

  • content-addressable store
  • hardlink / symlink / junction

把包链接到项目里。

2. standalone 会把依赖「链接」进产物目录

生成 .next/standalone/node_modules 时,Windows + pnpm 下经常会出现:

text 复制代码
.next/standalone/node_modules/next  →  指向真实的 node_modules/.../next

也就是说,standalone 里的很多目录,并不是独立拷贝,而是指回真实依赖

3. 清理时错误地「递归删除」了链接目标

下一次执行:

  • next build(清理旧 .next / standalone)
  • 或某些情况下的 next dev

Next 会尝试删掉旧的 .next/standalone

关键问题是:递归删除时没有正确处理目录 symlink/junction,于是:

text 复制代码
删 .next/standalone/node_modules/next
  ↓ 跟随链接
把真实 node_modules 里的 next(甚至 react 等)内容删空

于是你看到:

  • junction 还在
  • 文件没了
  • next 自己把自己删了

Linux / macOS / CI(通常在 Linux 容器)往往不容易踩到,所以会出现「同事都没事,只有我 Windows 本机天天重装依赖」。


官方有没有修?

有,但不是你现在这个老版本能直接吃到的

修复思路就是:删除时避免顺着目录 symlink 把真实包内容清掉。

但如果你还在 next@14.1.4 这类版本,这个修复并不在其中。

要官方修复生效,需要升级到包含该 PR 的较新 Next(大致在 15.4+ / 含修复的 canary)。

对存量业务来说,仅仅为了这个 bug 立刻大版本升级,往往成本偏高。

所以短期用 workaround 更务实。


实用解决方案

方案 A:启动前先删掉危险目录(推荐短期方案)

dev / build 前主动删除 .next/standalone,避免 Next 自己「跟链路删除」。

核心脚本示例:

js 复制代码
// scripts/clean-standalone.mjs
import { existsSync, rmSync } from "node:fs";
import { join } from "node:path";

const standaloneDir = join(process.cwd(), ".next", "standalone");

if (existsSync(standaloneDir)) {
  rmSync(standaloneDir, { recursive: true, force: true });
  console.log("[clean-standalone] removed .next/standalone");
}

package.json

json 复制代码
{
  "scripts": {
    "clean:standalone": "node ./scripts/clean-standalone.mjs",
    "dev": "node ./scripts/clean-standalone.mjs && next dev",
    "build": "node ./scripts/clean-standalone.mjs && next build"
  }
}

注意:

  • 清理发生在 next build 之前
  • next build 成功后仍会重新生成 standalone
  • 因此对 Docker / CI 打包通常没有破坏性影响

唯一要注意:如果你本机「刚 build 完 → 立刻 pnpm dev」,dev 会先清掉 standalone。

需要产物时,先打包/拷贝,再开 dev。

方案 B:长期升级 Next

升级到包含官方修复的版本后,再验证:

bash 复制代码
pnpm build
pnpm build
pnpm dev

确认不再误删依赖,再考虑去掉清理脚本。

方案 C:本地关闭 standalone(架构更干净,但要拆配置)

如果本地开发不需要 standalone,可以:

  • 本地 next.config 不开启 output: "standalone"
  • 只在 Docker / 生产配置中开启

这样从根上减少本机触发面。

代价是要维护「本地配置 / 生产配置」的差异,并确保 CI/Docker 仍能正确产出 standalone。


排查清单(下次再遇到时)

  1. 看报错是不是 next/dist/pages/_appnext/dist/bin/next
  2. 检查 node_modules/next 是否「目录在、文件空」
  3. 确认 next.config 是否开了 output: "standalone"
  4. 确认是否 Windows + pnpm
  5. 先删 .next/standalone,再决定是否需要重装依赖
  6. 若已被删空,只能 pnpm install(必要时强制重建被清空的包)恢复

一句话总结

这不是你依赖没装好,也不是 pnpm 的锅,更不是 Windows 故意的------ 这是 Next.js 在 Windows + pnpm 组合下的清理逻辑太"耿直"了,耿直到把自家的包都给端了。

官方在较新版本已修复;旧版本项目用「build/dev 前清理 .next/standalone」即可稳住日常开


参考链接

相关推荐
HjhIron1 天前
手把手教你用 Next.js 14 + Redis 从零搭建一个全栈 Markdown 笔记系统
前端·全栈·next.js
To_OC2 天前
写了三天 Next.js,我的 React 世界观被拆了又装回去
前端·全栈·next.js
名字还没想好☜2 天前
Next.js 用 Server Components 直连数据库:去掉 API 层的边界,和三条别踩的安全红线
前端·javascript·数据库·安全·react·next.js
不知疲倦的老鸟2 天前
Next.js 15 多语言站点的 6 个坑:从 query string 路由到 URL 路径
typescript·next.js
JieE2122 天前
Next.js App Router 全栈实战:从零构建一个 Markdown 笔记系统
全栈·next.js
DsirNg2 天前
React Server Components 在真实项目中的边界:哪些组件该放在服务端
性能优化·react·next.js·app router·前端架构·rsc·react server components
Asize3 天前
为什么写代码前要先规划组件树?Next.js + Redis 笔记系统实战
前端·javascript·next.js
小林ixn3 天前
用 Next.js 和 Redis 撸一个 Markdown 笔记系统:RSC 实战与组件化拆解
前端·redis·next.js
嘟嘟07175 天前
Next.js 里 SSR、CSR 和水合到底差在哪?从一段待办代码说起
前端·后端·next.js