Next.js 作为目前最受欢迎的 React 全栈框架之一,凭借其强大的功能和“开箱即用”的体验赢得了大量开发者。然而,这种便利性很大程度上依赖于其创造者 Vercel 提供的原生托管平台。但是一旦开发者选择通过 Docker 自行部署,便会发现这条路并非坦途,充满了各种不易察觉的“坑”,并且会逐渐感受到框架本身的臃肿与对特定平台的深度绑定。
这里就总结了一下我这一年实践遇到的问题,建议没有 SEO 需求还是不要碰他了。
路由与请求类型的“黑盒”
在自行部署的环境中,Next.js 的一些内部机制会变得异常棘手。以 React Server Components (RSC) 的请求为例,它与页面的请求路径一致,但会附加一个 ?_rsc=... 参数以区分(也会增加一些 header)。问题在于,这个参数(以及额外的 header)在到达我们自己的应用逻辑之前,往往已被 Next.js 框架内部“消化”。这意味着,在proxy.ts(16 之前的middleware.ts),无法捕获到 _rsc 这个参数。因此,从代码层面来看,开发者无法轻易判断一个传入的请求究竟是完整的页面加载请求,还是一个用于更新部分 UI 的 RSC 请求。
这在一些重定向场景中会造成一些问题。
缓存优化:自己部署可能沦为“负优化”
Next.js 提供了多种强大的缓存策略,如静态站点生成 (SSG) 和增量静态再生 (ISR)。这些功能在 Vercel 平台上能够发挥最大效用,因为 Vercel 将缓存部署在全球边缘网络上。然而,当使用 Docker 自行部署时,情况就大不相同了:
- 内存显著增加:SSG 页面和数据缓存在默认情况下会存储在服务器的文件系统或内存中。对于拥有大量页面的应用,这会导致构建产物的巨大,并在运行时占用大量内存。
- 边缘优势丧失:Vercel 的缓存位于边缘节点,自行部署时缓存则位于源站,失去了边缘计算带来的低延迟优势。
不稳定的缓存:从开发到生产的鸿沟
除了基础设施层面的挑战,Next.js 的缓存机制本身也给开发体验带来了巨大的不确定性。
开发与生产环境的巨大差异:
next dev命令为的是极致的开发体验和快速反馈,因此它在默认情况下会禁用或绕过绝大部分缓存。开发者在本地修改代码,刷新页面,总能看到最新的结果。然而,next start运行的生产环境则会启用激进的缓存策略。这就导致了大量“本地正常,线上异常”的诡异问题,例如数据更新不及时、页面内容陈旧等。API 与行为的持续剧变:缓存是 Next.js 近几个大版本中变动最频繁、最不稳定的部分。 最新的 16 版本中引入的
Cache Components和"use cache"又取代了之前的dynamic和revalidate参数。 这种频繁的迭代意味着每个大版本都可能带来缓存行为的破坏性变更。开发者刚在一个版本中掌握的缓存逻辑,到下一个版本可能就需要重构。这种不稳定性让开发者难以建立稳定的预期,对于需要长期维护的项目来说,无疑是一颗定时炸弹。
过高的心智负担与非标准抽象
上述所有技术挑战最终都指向一个核心问题:过高的心智负担。开发者本应聚焦于业务功能的实现,却不得不分出大量精力去理解和解决与业务无关的、纯粹由框架及其快速迭代带来的复杂性。
更关键的是,许多导致这些问题的 Next.js 功能,其概念和标准 HTTP 协议并不直接对应,而是 Next.js 独有的抽象。例如,RSC 的请求机制、Server Actions 的 RPC 调用方式、缓存设计等,这些都不是通用的 Web 标准。这意味着:
- 问题排查困难:当出现问题时,你不能完全依赖对 HTTP、CDN 和浏览器工作原理的通用知识,而必须深入了解 Next.js 的内部实现。
- 知识的可移植性低:你花费大量时间掌握的关于 Next.js 缓存、路由和运行时特性的知识,在切换到另一个框架或技术栈时,其价值会大打折扣。
平台绑定与“厂商锁定”的担忧
许多 Next.js 的核心功能实际上是为其原生平台 Vercel 量身定做的,这引发了社区对于厂商锁定(Vendor Lock-in)的担忧,例如与 Vercel Edge Runtime 绑定的中间件(现在的 proxy.ts/middleware.ts 可以跑在 node.js runtime 了…)、深度整合 Vercel 图片服务的 next/image 组件等。
结论
自行使用 Docker 部署 Next.js 并非不可能,但开发者必须清醒地认识到这其中隐藏的复杂性与额外成本。这不仅是基础设施的物理成本,更是开发团队心智负担的隐性成本。
Next.js 的发展方向越来越像一个与其托管平台深度绑定的全栈解决方案。它在标准 Web 技术之上构建了自己的一套独有、且快速变化的抽象,虽然在 Vercel 平台上这套抽象能提供极致的便利,但在平台之外,开发者就需要为这些抽象的复杂性、不稳定性以及与生产环境的脱节而买单。对于追求技术栈完全控制、希望团队知识能够长期复用、或旨在避免厂商锁定的团队来说,在选择 Next.js 之前,必须仔细评估其“全家桶”模式是否真的符合项目需求。有时候,一个更轻量、更专注、更贴近 Web 标准的框架,或许是更务实的选择。
希望 tanstack 可以做到吧。
参考资料
API Reference > Directives >use cache
Next.js RSC _rsc 参数丢失的那些坑以及解决方案