96SEO 2026-04-29 08:22 6
那是一个kan似平静的下午,窗外的阳光正好,我正沉浸在 My-Notion 项目的开发节奏中。这是一个 AI 原生的个人知识库项目,一切dou在按部就班地进行。本地开发环境跑得顺风顺水,功Neng测试也没发现什么大坑。我心想,这次迭代稳了顺手就把代码推到了 GitHub。

然而还没等我端起手边的咖啡,钉钉告警群就开始疯狂闪烁。紧接着,GitHub Actions 的构建界面那个刺眼的红叉映入眼帘——CI/CD 构建直接失败了。那一刻,我的心情就像过山车一样瞬间跌入谷底。明明本地跑得好好的,怎么一上流水线就炸了?这种“薛定谔的 Bug”Zui让人抓狂。
排查:从懵圈到怀疑人生我第一反应是是不是服务器抽风了?还是网络波动导致依赖下载失败?带着这种侥幸心理,我点开了构建日志。满屏的报错信息中,一行字格外扎眼:
Error: Cannot find module '@qdrant/js-client-rest'
这就hen奇怪了。我明明记得在本地引入过这个包,而且运行的时候也没报错啊。难道是我在Zuo梦?我迅速切回本地终端,运行了一下启动命令,项目稳稳地跑在了 localhost 上。这就geng让人摸不着头脑了:本地Neng跑,线上不行,这是典型的环境不一致问题。
为了搞清楚状况,我决定像侦探一样回溯一下操作路径。这个项目里用到了 Qdrant 向量数据库,相关的代码逻辑在 packages/ai/rag/qdrantVectorStore.ts 文件里。我打开文件,kan到了这行代码:
import { QdrantClient } from "@qdrant/js-client-rest";
代码写得清清楚楚,引用了 @qdrant/js-client-rest。既然代码里用了那按理说 package.json 里应该有声明才对。于是我又打开了同级目录下的 package.json,结果却让我大吃一惊:
{
"dependencies": {
"@langchain/qdrant": "^0.0.5",
// ... 其他依赖
}
}
注意到了吗?@qdrant/js-client-rest 这个包根本就没有出现在依赖列表里!这就好比你在家里用着邻居的电视,却以为那是你自己买的一样。这就是传说中的“幽灵依赖”。
既然代码里用了却没声明,那这行代码是谁写的?我陷入了沉思。哦,想起来了之前为了赶进度,我让 AI Agent 帮我处理 Qdrant 相关的集成。
我翻了一下当时的对话记录,发现了这样一段指令:
我:帮我安装 @langchain/qdrant
Agent:npm install @langchain/qdrant ← 罪魁祸首!
问题找到了!我的 AI 居然自作主张用了 npm install,而不是我项目规范要求的 pnpm add。这kan似只是命令行的区别,背后却隐藏着巨大的坑。
npm 在安装 @langchain/qdrant 的时候,会顺带把它的依赖 @qdrant/js-client-rest 给装上。geng关键的是npm v3+ 采用了扁平化的安装策略。它会把这个间接依赖“提升”到 node_modules 的根目录下。
于是本地的目录结构变成了这样:
node_modules/
├── @qdrant/js-client-rest/ ← 被提升上来了你的代码Neng直接访问
├── @langchain/qdrant/
│ └── node_modules/
│ └──
├── langchain/
├── openai/
└── ...
AI Agent 在写代码时直接 import 了 @qdrant/js-client-rest。在本地环境下因为 npm 的扁平结构,这个包就赤裸裸地躺在根目录下代码“kan得见”它,所以运行一切正常。这种“kan起来没问题”的假象,就是Zui大的陷阱。
为什么本地没问题,一上 CI/CD 就炸了呢?因为我的 CI/CD 环境配置的是 pnpm。
pnpm 的设计哲学和 npm 完全不同。它不搞扁平化那一套,而是采用软链接 + 硬链接的方式,构建了一个严格的依赖树。在 pnpm 的世界里每个包只Neng访问自己声明的依赖,或者是它依赖项的依赖,绝不允许“越界”。
Ru果用 pnpm 安装,目录结构会是这样:
node_modules/
├── .pnpm/ ← 真实存储位置
│ ├── @qdrant+js-client-rest@1.0.0/
│ │ └── node_modules/
│ │ └── @qdrant/js-client-rest/
│ └── @langchain+qdrant@0.0.5/
│ └── node_modules/
│ ├── @langchain/qdrant/
│ └── @qdrant/js-client-rest/ ← 软链接,只有 @langchain/qdrant Neng访问
├── @langchain/qdrant/ ← 软链接到 .pnpm
├── langchain/ ← 软链接到 .pnpm
└── ← 你的代码找不到它
kan到了吗?在 pnpm 的结构下@qdrant/js-client-rest 被严格地隔离在 @langchain/qdrant 的私有作用域里。我的代码Ru果没有显式声明依赖它,根本就kan不到这个包。所以一旦代码跑到线上构建环境,pnpm 立刻就翻脸不认人,直接抛出模块找不到的错误。
这让我想起隔壁组 Java 同事的遭遇。他们有个服务在 K8s 上部署,平时好好的,结果大促那天凌晨,群里突然炸了:订单服务 POD 重启,内存直接打满被杀。那种线上崩溃的绝望感,和我现在面对构建失败的心情如出一辙。虽然一个是内存溢出,一个是依赖缺失,但本质dou是一样:开发环境的“宽容”掩盖了隐患,等到生产环境的“严苛”条件一来立刻原形毕露。
对比:npm 与 pnpm 的本质差异为了geng直观地理解,我把两者的区别整理了一下:
| 特性 | npm | pnpm |
|---|---|---|
| 依赖结构 | 扁平化,间接依赖提升到根目录 | 严格隔离,只Neng访问声明的依赖 |
| 幽灵依赖 | 本地不会报错,线上可Neng炸 | 开发阶段直接暴露 |
| 安装速度 | 较慢 | 快 |
| 磁盘占用 | 每个项目独立存储 | 全局存储,多项目共享 |
幽灵依赖的本质,就是依赖声明和实际使用不一致。npm 的扁平结构就像是一个大杂烩的仓库,谁douNeng拿东西用,虽然方便,但容易乱;而 pnpm 则像是给每个包配了独立的保险柜,没钥匙的别想拿东西。虽然前期多写几行 package.json 有点麻烦,但换来的是部署时的安心。
找到了病根,接下来就是对症下药。既然是因为 AI 用错了包管理器,导致残留了 npm 的扁平结构和幽灵依赖,那我就得彻底清理一遍。
第一步:大扫除我要把本地那些乱七八糟的 node_modules 全部删掉,特别是可Neng残留的 package-lock.json,那是 npm 的遗留物,必须清除。
# 删除所有 node_modules
find . -name "node_modules" -type d -prune -exec rm -rf {} +
# 删除 lock 文件
find . -name "package-lock.json" -delete
# 用 pnpm 重新安装
pnpm install
这一步操作下来pnpm 的严格结构会立刻生效。Ru果代码里还有其他未声明的依赖,这时候就会一个个暴露出来跑dou跑不起来。
第二步:显式声明依赖针对报错的 @qdrant/js-client-rest,Zui根本的解决办法就是:用了什么就老老实实声明什么。不要指望别人的包里带着你用。
pnpm add @qdrant/js-client-rest
执行完这条命令,package.json 里终于有了它的名字:
{
"dependencies": {
"@langchain/qdrant": "^0.0.5",
+ "@qdrant/js-client-rest": "^1.0.0"
}
}
这时候, 运行构建,红线终于变成了绿勾。那种如释重负的感觉,简直比发工资还爽。
防范:如何避免 踩坑?吃一堑,长一智。为了防止以后再出现这种低级错误,尤其是防止 AI Agent “背刺”我,我决定采取一些geng主动的防御措施。
1. 约束 AI Agent 的行为问题的根源在于 Agent 用了 npm install。为了防止再犯,我给 Agent 写了一个全局 Skill,明确规定:后续所有安装包的操作,必须强制使用 pnpm。
我在 Prompt 里加了一条死命令:“在这个项目中,永远使用 pnpm add 来安装依赖,严禁使用 npm install。” 这样 Agent 每次对话dou会读取这条规则,就不会再出现工具选型错误的问题了。
除了等 pnpm 报错,我们还Ke以用工具主动出击。这里推荐两个神器:knip 和 dpdm。
knip 工具
knip Ke以检测未使用的依赖、未声明的依赖以及各种死代码。它就像是一个严厉的代码审查员,帮你把那些藏在角落里的隐患揪出来。
npx knip
dpdm 工具
dpdm 则专注于依赖关系,它Ke以扫描代码中的依赖引用,反向找出那些你在代码里用了、却没在 package.json 里声明的依赖。
npx dpdm src/index.ts
有了这两个工具,基本上Ke以把幽灵依赖扼杀在摇篮里。
3. 严格检查 Peer Dependencies在安装时加上 --strict-peer-dependencies 参数,Ke以让 pnpm 对依赖冲突零容忍。
pnpm install --strict-peer-dependencies
Ru果有冲突,它会直接报错而不是静默跳过。虽然这可Neng会让安装过程稍微“坎坷”一点,但总比上线后炸锅要强。
这次“线上部署爆炸”事件,虽然Zui后有惊无险地解决了但给我的教训是深刻的。hen多时候,我们在本地开发时觉得“没问题”,其实是因为环境太“宽容”了。npm 的扁平结构掩盖了依赖管理的混乱,让我们产生了一种“代码Neng跑就行”的错觉。
而 pnpm 的设计初衷,正是通过严格的依赖隔离,在开发阶段就强制你面对所有的问题。它不让你偷懒,不让你依赖运气,逼着你写出geng规范、geng健壮的代码。
试想一下Ru果这次没有 pnpm 在 CI/CD 环境里拦这一刀,这个幽灵依赖可Neng会在某个关键节点突然爆发。到时候,可Neng就不只是构建失败这么简单了说不定会导致线上服务异常,那时候再排查,成本可就是指数级上升了。
所以别再抱怨 pnpm 报错了。它每一次报错,其实dou是在救你的命。对于任何线上产品来说性Neng测试、依赖检查、环境一致性验证,这些dou不是多余的流程,而是必须坚守的底线。你不知道用户会在什么场景下使用你的软件,也不清楚系统承受的极限在哪儿,唯有严谨,才Neng带来真正的自由。
Zui后Ru果你也在用 pnpm + Monorepo,一定要检查一下你的 package.json,kankan有没有藏着什么“幽灵”。毕竟谁也不想在一个安静的下午,突然kan着告警群炸锅而急哭吧?
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback