为何 Next.js 难堪软件工程重任?

Next.js作为框架,虽被宣传为React框架,但社区始终将其视为全栈框架。我认同这既是社区认知偏差,也是技能问题。

 💬 118 条评论 |  Next.js/React | 

写作时间难觅,尤其当肩负更多责任且需照顾家庭时。但当Vercel这样的大公司CEO征询真诚反馈时,绝非一条推文能概括。故此,谨呈完整回应:

我对将Next.js作为通用React项目框架持保留态度。若推荐使用,我认为这有违软件架构的本质。三四年前在多个产品中采用该框架后,我曾撰文阐述过相关挑战:

You Don’t Need Next.js (and SSR)

Next is an amazing framework but there are limited use-cases where you should embrace it. Picking right tool for the…

这并非对React或Vercel公司的批评。Vercel打造了卓越的平台——从托管服务到V0版本,再到赞助优秀开源项目。我的批判仅针对Next.js作为抽象层的特性,以及其思维模型与架构为何常阻碍可持续系统的构建。

若您不重视软件架构或优质软件工程,请在此止步。若您认为下文所述法则荒谬,同样请停止阅读。

元素周期表

软件抽象法则

仅当满足以下任一条件时才应构建抽象层:

  • 底层系统复杂,需隐藏其实现细节(如HTTP协议封装TCP协议,使我们无需考虑二进制序列化)
  • 底层系统不完整,需新增功能(如TCP协议对无连接的IP协议进行抽象并提供连接保障)

若抽象未能满足上述任一条件,则该抽象存在缺陷或属于过度抽象。Next.js屡屡违反此原则。其抽象机制表面便捷,但应用于大型模块化系统时便迅速失效。核心问题在于:Next.js究竟在抽象什么?

优秀框架的本质

框架是实现恰当抽象层次的机制。相较于库,框架作为更高阶的构造体,通过剥离控制权并提供合理约束与流程来实现价值。

何为优秀框架?其具备三大核心特质:首先,优秀框架提供良好的分层架构(亦称洋葱架构),逐层剥离能提升复杂度同时赋予精细调优的控制权;其次,优秀框架提供清晰定义的生命周期;最后,优秀框架能预防错误,使常规任务变得枯燥。

Next.js作为框架,虽被宣传为React框架,但社区始终将其视为全栈框架。我认同这既是社区认知偏差,也是技能问题。框架开发者的职责不仅是提供代码,更需配套完善文档与规范指南。Next团队在指导方面存在不足,有时我甚至担心他们暗中引导开发者为其平台创造价值。

我将以Next.js的复杂度与可组合性作为分析基础。此前我已在另一篇文章中对此进行了详尽阐述,在此不再赘述。

复杂性与思维模型

Next.js 将所有渲染模式(即执行上下文)——CSR、SSR、SSG 和 ISR 整合为一体,其上还叠加了流式处理功能。由此衍生出“一切都需要 SSR”的谬误建议。诚然,身份验证等操作确实需要服务器支持,但渲染并非如此。试问有多少项目像亚马逊那样,每个页面都需要同时提供公共视图和认证视图?除非明确禁用,否则默认渲染模式始终是服务器端。

在我的思维模型中,必须时刻关注时间轴与运行时轴——我究竟身处何处?是在构建周期(超前阶段)、服务器周期,还是客户端周期的某个节点?Next.js 摒弃了简单生命周期概念,转而强制使用特殊函数。这些函数隐式存在且由编译器驱动,而非应用驱动。这些神奇的入口点始终难以良好组合。长期来看,此类系统的逻辑推演绝非易事。基于文件系统的路由机制尚可接受,但仍易引发冲突与错误。

诚然这可能源于我的技术不足,但框架难道不该阻止我犯错?至少也该让一切变得显而易见且简单无趣?我渴望框架能引导我前行,而非迫使我深陷其复杂性与文档海洋。

核心观点:软件工程中,代码运行位置与系统组合逻辑的清晰性至关重要。Next.js模糊了这些边界。开发者不得不同时处理构建/请求/客户端/服务端的多重模型,还得应对特殊规则和隐性约束。

其结果并非可抛弃的接口,而是耗费高昂成本构建和维护的精心打理的代码库。

可组合软件

我能接受复杂性,这正是我从事咨询工作时获得报酬的原因,但当我开发企业级软件时,情况就不同了。我面临六大痛点:

  • 分层架构与耦合性
  • 可插拔架构
  • 12要素与环境变量
  • 生命周期与初始化
  • 自定义服务器与中间件
  • 打包与复用性

首先谈分层架构。Next.js基于四大核心支柱构建:

它由四个核心组件构成——CLI/脚手架、打包器/编译器、路由器以及补充React.js的运行时环境。

在良好的分层系统中,这些组件均可替换或扩展。但在 Next.js 中,这种分离并不存在。

  • 替换路由器?理论上可行,但操作体验极差。
  • 更换打包器?技术上可行,但系统脆弱且不受支持。
  • 使用其他 CLI?必然伴随巨大阻力。

这种紧密耦合意味着我无法在不影响其他层的情况下“窥探”某一层。在企业系统中,这违背了软件工程的核心原则:松耦合与明确定义的接口。

但请您自行判断!Next.js的分层是否足够?松耦合是否已不再是优秀软件工程的组成部分?这难道不是值得追求的准则吗?分层架构至关重要,它凝聚了数十年的软件工程智慧。松耦合至关重要。在大型软件系统中,若无法对某部分进行实验性替换,创新将面临重大障碍。我需要构建能留出创新空间而不扰乱全局的系统架构。作为资深工程师,我需在团队中平衡业务需求、技术学习与创新机遇。没有CTO会允许我重写正在运行的系统。

紧密耦合总以意想不到的方式作祟。某项目从Webpack迁移至Vite耗时6-7个月——包含开发、测试、回滚、修复及最终部署。

或许Next正试图成为JavaScript/TypeScript领域的Rails、Django甚至Spring Boot!但若果真如此,我仍有更多痛点待解。

可插拔架构

以一个极其简单的Keystatic CMS集成为例。这是Astro集成方案,而这是Next集成方案。Astro提供清晰的插件接口:CMS集成可自行注册而不渗入应用程序代码。而Next.js恰恰相反:

在Next.js中,我必须显式配置管理页面,导致CMS细节渗入代码库。

这根本算不上可插拔架构。即便只是几行代码或一个文件,也会渗透到代码库的每个角落。这是抽象层泄露。我想构建一个监控库,却无法直接通过Next.js配置实现。我必须添加配置项,再要求库用户添加instrumentation.ts文件,最后祈祷他们能正确使用。又一次抽象层泄露。

12要素与环境变量

自三年前我在StackOverflow提问至今,该框架仍未能开箱即用地解决这个问题:

How to enable 12-factor application bundling with Next.js?

Reading the Next.js documentation about environment variables – In order to keep server-only secrets safe, Next.js…

stackoverflow.com

理论上可以实现,但实践中已不可行。许多辅助库如今都依赖于NEXT_PUBLIC_*变量的使用。现在即便我确保代码不使用构建时变量,也必须为每个环境单独构建镜像/容器——这一切都源于这些库的限制。别无他法。任何超出NODE_ENV需求的方案都值得商榷。

若询问C/C++开发者,他们会立刻指出:构建时变量本质上等同于构建时宏展开。这类变量仅适用于高级场景,需谨慎使用,且仅适用于100%静态渲染。

对于其他渲染模型,你完全可以将变量内联到初始负载中,且不会造成任何性能开销——字面意义上的零开销。

Next竟将此设计为核心特性令人费解。框架的职责本应是防范意外错误。

一次性初始化

历经15次重大迭代后,官方仍未提供启动或一次性执行事件的解决方案。由于无法确定最终打包输出的形态,我最终只能通过globalThis对象的唯一标识符来确保单例模式。

我已见过至少六个项目使用instrumentation.ts文件进行一次性代码初始化。这些框架真的实现了应有的功能吗?

框架的本质在于剥夺开发者控制权并提供生命周期管理。Hono.js有生命周期,Fastify同样如此——事实上Fastify拥有设计最完善的生命周期之一。

若Next.js仅处理渲染问题,它本应成为成熟框架的简单适配器,但显然它未能解决所有问题,这引出了我的下一个观点。

自定义服务器与中间件

所谓“自定义服务器”实为名不副实。我们尝试过,曾与Koa配合使用。我们尝试过在 Fastify 中使用它,但实现过程中所需的思维体操实在得不偿失。它能工作直到无法工作,某些功能根本无法实现。

接着是中间件。中间件支持 Node.js 运行时花了 15 次迭代才实现,此前唯一选择是某种晦涩的 Edge 运行时。中间件在 Express 时代就是个已解决的问题。若某框架自诩为解决方案,这些问题本应早已解决。重新发明轮子毫无意义。

再次强调:若Next.js仅解决渲染问题,它就该作为成熟框架的适配器/插件/中间件存在。

早期我曾用Webpack构建过自己的SSR方案。在非Node.js运行时的后端集成中,我使用过Vite。如今我正尝试结合Fastify使用Vite的SSR方案,同时也在探索Parcel新推出的React服务器组件。这些都是经过深思熟虑的可组合解决方案,能无缝融入现有生态。我的Docker镜像无需特殊配置,也不必将Web应用拆解为多个服务——因为Next.js并未为Web应用的其他核心需求提供更优的抽象方案。它就是这么简单好用。

我由衷钦佩@pilcrowonpaper及其在Lucia Auth项目中的贡献。虽未曾谋面,甚至不知其真名,但深知他毫不犹豫地舍弃既有方案,将其转化为一套设计规范——只因从架构层面看,这才是更合理的选择。请继续阅读以深入理解:

A fresh start · lucia-auth lucia · Discussion #1714

It’s official – I’ll be deprecating Lucia v3 by March 2025. lucia NPM package will be maintained until March (mostly…

github.com

Next.js模糊了构建时与运行时环境变量的界限。Next.js团队未提供更完善的用法指导,这阻碍了12要素部署的实现。难道这不是框架理应具备的标准功能吗?无论是否属于UI框架都应如此。

这本应是Next.js的理想形态。它本可解决复杂的动态加载与数据流问题,但无需为此构建完整框架。

应用程序打包

最后一点我将就此打住。我仍有未解之惑:

How to package Next.js application as a reusable Node library?

I have built a next.js application with some bare minimum functionality that includes: Middleware for authentication…

stackoverflow.com

我所能做的只是发布一堆函数和钩子,希望用户能通过文档指引在正确位置做正确的事——因为Next.js既未提供恰当的抽象层级,也未构建完善的应用组合体系,更未提供可挂钩的应用生命周期。

正如我所言,问题清单很长,可以继续列举:

  • 僵化的文件系统路由机制
  • 神奇中间件及其在访问公共文件、API路由时的怪异行为(且随渲染模式变化)
  • 缓存机制的诡异表现
  • 超出HTTP和tRPC范畴的任何需求
  • 条件繁多的僵化布局系统
  • 缺失状态化请求响应范式
  • 完全不支持线程、后台任务或队列
  • 贫民版API系统

单独看这些缺陷,似乎框架支持全面且某些限制尚可接受。但综合来看,它呈现出这样一幅图景:框架对渲染环节实施严密控制,却将其他所有问题都搁置未决。

渲染之外的世界

若您已读至此,最关键的批评在于:现实世界中存在大量应用场景,在Next.js的限制下根本无法实现。我亲身经历过数个此类案例:

  1. 动态主题系统(CRM用例)。
    类似WordPress或Ghost的需求是:用户需能上传主题(CSS、模板、资源)而不需修改代码或重新部署。由于Next.js将渲染与构建步骤紧密耦合,这成为不可能任务。最终我只能在Next.js外部编写自定义JSX模板系统。
  2. 多团队模块化平台。
    某公司需要多个小型应用,由不同团队分别负责。理想方案是将基础应用发布为 NPM 包并进行扩展。Next.js 不支持这种模式。解决方案是每次升级都使用脚手架工具和代码转换器——经历九次迭代后,该公司最终将 Next.js 从技术雷达中移除。
  3. 企业级发布管道(金融领域)。
    在受监管的金融环境中,Docker镜像必须从集成环境→预发布环境→生产环境逐级推进且不可重建。Next.js基于灵活重部署的设计理念,导致此流程无法实现。该框架的部署模型根本不符合企业级发布规范。
  4. 双重许可/开放核心产品
    我需要构建一个核心开源(不含认证、日志和遥测功能)而专有版本扩展这些功能的产品。Next.js的单体应用模型使得不复制整个应用程序就无法实现这种设计。最终我不得不构建两个独立代码库并使用Git子模块。

重新审视抽象法则与Next.js

因此,重新审视我的抽象法则。Next.js究竟解决了什么问题?它最多只是对打包器和React渲染生命周期进行了抽象。但这些抽象存在泄漏,难以推导,且引入的复杂性远超其消除的复杂性。对大多数应用而言,这种权衡并不值得:

  • 它不如Vite简单透明。
  • 不如 Phoenix 或 Rails 全面
  • 实际无法支持模块化的大规模架构

由此只剩三种可能:

Next.js 未能满足核心软件工程标准。我的架构认知存在偏差。或者 Next.js 存在某种“宏大愿景”,只是我与许多人尚未领悟。

结论

作为拥有近十六年开发经验的Web应用架构师,现任首席/资深工程师,我常与两类技术领导者共事并直接向其汇报。若CTO宣称“全员都在用Next.js,我们也采用吧”,我会接受决策并确保落地。但若其他领导者征询我的架构评估意见,我的观点如下:

Next.js确实支撑着我的生计,我也能用它交付项目。但它的崛起令人联想到面向对象编程的热潮:快速普及、狂热追捧,以及对其魔力近乎盲目的信仰。真正的挑战在于:在这种氛围下,严肃的质疑往往难以提出(参见原文讨论;所有问题都被简单归咎于技术能力不足)。此言非出自我口,但据我所记,保罗·格雷厄姆或埃里克·雷蒙德曾有类似论述。

若Next.js坦诚定位为UI渲染适配器,其设计选择尚可理解。但当它被包装成全栈(或其他类型)框架时,便持续违背成熟的软件工程原则:可组合性、模块化与架构清晰度。

我的观点并非极端主义。我不推崇微前端模式——那种无需真实部署就能空中推送更新的做法。我也反对DHH的“不打包、不编译”理念。更不认同Next.js构建用户界面的方式。我真正倡导的是简约之道:重视打包但排斥魔法打包;重视编译但拒绝编译器主导的生命周期;接受框架掌控但要求其提供清晰可靠的应用生命周期。

正因如此,Next.js于我而言并非真正的框架——它不过是披着框架外衣的渲染工具。尽管它能满足许多团队的基本需求,但我无法推荐其作为大型、架构优良应用程序的基础。在软件领域,简约永恒。本文目的并非指定唯一替代方案或评判优劣,而是揭示实践中观察到的权衡取舍。

本文文字及图片出自 Why Next.js Falls Short on Software Engineering

共有 118 条讨论

  1. 老兄,我记得当初满耳都是Next有多厉害,现在却反差巨大。

    1. 它在基本取代Gatsby成为主流时确实很棒,之后很长一段时间也保持着优势。它在SSR、静态生成、增量生成等酷炫技术上做得相当出色

      后来却变得过于复杂,加上Vercel作为官方/最佳托管方案也帮不上忙。最近他们虽在加强自托管文档支持,但企业用户仍遭遇生产环境性能问题和各种陷阱

      作为多年用户,目睹其衰落令人扼腕,我也已不再推荐它,这点与你帖子所述相符

      1. 现在你推荐什么方案?我用Next.js搭建了几个站点,但开发阶段尚早,或许还能换其他框架

        编辑:我的具体场景是需要与无头Drupal配合的React(或类似)框架

        1. 内容密集型网站首选Astro。若不习惯纯JS编写或需处理复杂客户端交互,建议搭配Alpine使用。

            1. 另一方面,Alpine允许编写标准HTML5代码,这对SEO有利。无需编译步骤,内容在页面加载时由浏览器即时渲染,而非通过JS动态生成。

              我不了解GitHub渲染代码的情况。这与当前讨论有何关联?

            2. 我不了解GitHub渲染代码的情况。这与当前讨论有何关联?

              我能想象这会让代码审查变得更加痛苦。

            3. 关于那条Hacker News评论提到Alpine代码散落在字符串中,虽然Alpine文档确实大力推荐这种做法,但完全可以将代码放在

            4. 关于内联语法,这确实是我对Astro的顾虑之一。Astro的语法在IDE中支持得如何?

            5. VS Code支持非常出色。其他IDE就不清楚了。

            6. 这远不及 React 的支持程度——毕竟它本质上只是 TypeScript 生态的一部分。

              若您对VS Code满意则尚可,但其他工具并不理想。我的博客虽用它编写,但配套工具确实匮乏。即便工具欠佳,相比nextjs它仍是梦幻般的开发体验

        2. 目前唯一能以React 19方式实现功能的替代方案似乎是react-router,它整合了Remix。

          1. 你说“React 19方式”是指服务器端组件吗?

            每当看到有人想尝试新框架,我总要问:你是否真的需要服务器端渲染?很多情况下,人们构建的是登录后的用户旅程,此时SSR根本无关紧要。

            即便需要SSR,是否非得用服务器端组件?如今数据获取库已相当成熟,RSC提供的价值微乎其微,反而让你不必纠结服务器/客户端组件的边界问题。

            1. 没错,我指的就是服务器组件。你说的确实有道理——如果需要维护独立的公共API的话。但在我看来,RSC最大的潜在价值在于:多数场景下完全可以省去面向公众的API。让React服务器端直接与内部服务通信,通过服务器操作和服务器组件暴露数据,这样就永远不需要API网关之类的东西了。这并非要淘汰Tanstack Query等工具,而是要彻底消除API本身!若你仅为内部客户端暴露公共API(GraphQL、REST等),这种方案可能省去整套基础设施。我敢说很多公司都符合条件。

              遗憾的是,出于诸多原因,Next似乎并非理想的实验平台,因此尚不确定实际效果。我认为编写经典PHP应用与内部服务交互的体验,在理念和功能上与这种构建方式的React应用高度相似——而我记忆中那段时期的开发效率要高得多。

        3. 查看 vike https://vike.dev

          它超级灵活,满足各种需求:服务器端渲染、静态站点生成、客户端渲染、React、Vue,甚至能在同一个应用里混搭使用

      2. 完全同意。

        他们不再提供逃生通道,反而强化了“要么接受要么滚蛋”的立场。

        更让我抓狂的是他们根本不支持运行时环境变量——构建容器或包并部署到不同配置的多环境本是行业标准,现在却要大费周章才能实现。

        1. 他们根本不在乎运行时环境变量

          虽然能勉强实现,但方案糟糕透顶。若用冗长的环境变量占位符编译 Next.js 代码块,只要新值短于占位符且正确添加空字符终止,就能在代码块中替换它们。占位符需具备高熵值,若发现任何占位符重复出现,应直接构建失败。

          1. 笑死

            直接动态访问环境变量就能强制运行时生效。Next.js只替换字面量形式(如process.env.VARNAME),不会处理process.env[动态名称]这类动态引用。

      3. 我虽未实际投入生产环境使用,但在版本13时期做过教程/课程,当时颇为惊艳。后来新增功能层出不穷,感觉它已越过了某种复杂性临界点。

      4. 它绑定得太紧了。这和Rails的问题如出一辙——选择它就意味着被锁定在一系列决策中,这些决策未必适合你的项目。它更适合原型开发而非正式部署。

        我认为采用简单可组合的工具进行灵活搭配,远胜于依赖包罗万象的大型框架。

      5. 即便在客户端设备配备高端处理器和8-16GB内存的时代,我们仍需要服务器端渲染?1990年代的技术专家会嘲笑这种想法!这不过是应对日益膨胀的计算资源消耗的权宜之计,开发者们宁可依赖臃肿框架走捷径,也不愿优化应用性能。

    2. 这就是成为最大最流行事物的运作方式。没必要贬低那些无人问津的小众框架——它们既赚不到钱也吸引不了关注…但当它蜕变为人人使用、成为当今软件世界基石的超级框架时?抱怨声自然涌现,讨论时机便到了。

      这并非针对Next.js的评价,也无关我个人态度。只是行业周期的常态,未必意味着衰落。React经历过,JavaScript经历过,Java经历过,Python等等都经历过。

      仅供参考,爱听不听。但我认为人类天生更容易抓住问题和负面情绪,而非积极面。软件/工程领域的问题在于:所有技术都存在“权衡取舍”,缺陷本就是必然。这恰恰是我们工作的挑战所在。

      1. 框架只有两种:

        要么被人抱怨,要么无人问津。

    3. 欢迎来到现代网页开发,这是你第一次接触吗?

    4. 当它还是Next时令人惊艳。如今它成为主流框架,被非极客群体实际使用后,缺陷更易显现且被热议。很快它就会成为旧版框架。

    5. 曾几何时,NextJS确实解决了实际问题,那时的它确实很棒。

      当年React工具链如同不断重组的拼图,要构建现代工具链(TypeScript、ES7、ESLint)意味着每次依赖更新都要耗费 __ 数小时 __ 拼凑WebPack配置。直到NextJS出现,将整个过程抽象化处理。当时根本没有其他打包器可选。Vite、ESbuild、Parcel等工具尚未问世。

      他们通过简化React开发赢得了大量市场份额,随后却走上“垃圾化”道路——强行向用户施加一系列非必要的变更,而这些变更恰巧只能在其付费服务(Vercel)上正常运行。

      如今他们不再解决问题,反而采取教条主义态度,__ 强制规定 __ 应用架构必须遵循其“前瞻性”原则(如禁用SSR、禁止环境变量等),这种做法令人极度反感,与我使用过的任何开源软件都截然不同。

  2. 这是我首次亲耳听到有人指出构建版本永远无法从测试环境部署到生产环境。这个漏洞曾让我吃尽苦头——当时SRE团队暴跳如雷,坚称这种情况绝无可能发生。

    环境变量在构建时就被固化了。对此无能为力。你必须为每个环境重新构建。

    1. 在无法在运行时注入环境变量的k8s中部署NextJS真是太美妙了。或者…如果你愿意搞很多黑科技的话,其实也可以实现——我们就是这么干的。

      1. 确实某些场景可行,比如强制所有页面动态构建,但出错概率极高。更何况为实现可推广构建而做的种种操作,反而彻底否定了使用Next.js的初衷。静态内容、ISR、缓存甚至公共环境变量都存在安全隐患。

      2. 你们是否创建了config.json文件挂载到K8,再编写服务来拉取这些配置?

        1. 记得我们曾在Dockerfile里对入口点进行过修改,以调整构建输出中打包的环境变量。

    2. 这在多数实际部署的React环境中很常见。正是我对该生态系统(尤其React Native内部)最不满之处。

      若需在不重建时修改配置(如API地址或认证凭证),应在用户空间提供动态配置对象。读取 JSON 文件并非难事,部署脚本只需读取环境变量指定要读取的 JSON 文件即可——无论是在运行时、中间件、环境特定入口点还是开发菜单中实现。React 实现动态配置的方法多如牛毛,都比环境变量优越得多。

      1. 这在多数前端框架中很常见,解决方案是通过API获取环境特异性数据。

        对于服务器端框架,这毫无借口可言

        1. 你成功找到了比环境变量更糟糕的方案。完全不必要的API调用不该阻塞首次像素显示时间或响应时间。

    3. NEXT_PUBLIC_*环境变量已内置。其他变量按预期工作。有多种方案:

      • 构建时使用通用占位符值,容器启动时替换为实际值;

      • 在HTML中通过服务器渲染器注入全局window.__ENV(缓存路径无效!);

      • 在客户端代码启动时调用 API 路由获取环境变量子集。

      我曾用过所有这些方案部署应用,可能还有其他可用方案。目前采用第二种方案构建,根据我的经验运行最顺畅。但这依赖于路径未预渲染——而我的场景下本就无法预渲染。

    4. 这不可能是真的吧?!环境变量是向应用传递密钥的常见方式,你绝对不希望这些内容被固化到可部署包中。你的构建系统甚至不应接触这些变量。

      1. 这并非安全问题,因为环境变量需添加NEXT_PUBLIC_前缀才能在构建环境中公开访问。我指出的核心问题在于:构建产物无法从测试环境直接部署到生产环境,因为构建结果几乎总是包含基于环境特异性变量生成的渲染/烘焙信息。例如当存在测试环境和生产环境的CMS时,基于测试数据生成的静态渲染页面若直接用于生产环境显然会失效。

        我认同你的观点——构建系统本就不该要求访问此类数据。在构建时缓存真实应用数据的做法在其他框架中并不常见,通常仅限于Gatsby或Astro这类静态生成框架。正因如此Next.js也采用了这种方案,但考虑到Next.js本身是服务器端框架,这个选择确实有些奇怪。我更希望他们采用启动时缓存而非构建时缓存——虽然能解决当前问题,但会导致应用启动极度缓慢。权衡取舍…

      2. 这完全错误。公共前端变量会被固化,就像处理静态构建资源时常见的情况。此人纯属胡说八道。根本不存在被固化的密钥。

    5. 这纯粹是事实性错误,更暴露了对整个机制运作原理的惊人无知。

      首先:服务器端环境变量在运行时读取,这点与其他场景并无二致。显然它们不会被预编译。

      其次:前端环境变量之所以被预编译,是因为构建静态资源时 必须如此。你究竟打算如何在用户浏览器运行时切换环境变量?你可曾构建过客户端单页应用?它们面临着完全相同的问题。

      第三:Next.js 选择将 node_env 硬编码,我猜这正是你抱怨的点。对此人们可以有不同看法,但即便我们一致认为这是个糟糕的选择,也基本无关紧要。若真需要了解运行环境,只需使用自定义环境变量即可——不过严格来说不该这么做,因为这种需求往往意味着要在运行时进行条件判断,比如“如果是staging环境则使用这个URL,否则使用另一个URL”。你本不该这么做,而应该直接提供所需配置——无论是URL本身还是其他参数。开发环境中偶尔根据环境变量分发内容或许有用,但仅此而已。

      你跑来这里抱怨这件事,却暴露了你和你的SRE团队连两个脑细胞都没有,实在令人尴尬。

      1. 你写了这么多只是在印证我的观点。我只说环境变量会被烘焙进构建产物。正如你所言:环境变量会被烘焙进静态资源。这正是关键所在。任何缓存、ISR、静态页面等…都会将所有(不仅是公共环境变量)烘焙进去。除非页面完全动态化(即完全不使用缓存,甚至超越静态资源范围),否则服务器端环境变量不会被排除在外。

        这正是Next这类采用本地缓存机制的框架本质。Remix基本不受这些问题影响,因为它期望缓存发生在更高层级(CDN或浏览器缓存)。

        顺便说一句,你竟把我的行为称为“抱怨”,真是可悲又幼稚的爆发。

        1. 关键就在于此。任何缓存、ISR、静态页面等…都会将所有(不仅是公共环境变量)内容烘焙进去。除非页面完全动态化(即静态资源之外也完全不使用缓存),否则服务器端环境变量无法被排除在外。

          首先:服务器端环境变量根本不会出现在前端资源中。这道理对任何非智障人士都显而易见——否则岂不是把机密信息塞进前端资源?

          其次:你似乎完全不理解静态资源构建原理却仍坚持发表意见,这让我感到困惑。既然明显不懂相关机制,为何还要参与讨论?

          给你做个思想实验:假设你使用Vite并配置环境变量,为staging环境构建资源。你打算如何在不重新构建的情况下将其“推广”到生产环境?你做不到,因为这只是静态的js/html/css打包文件,环境变量已在构建时替换完毕——这本就是唯一可行的方案。这些代码在用户浏览器中运行,你根本无法在非服务器环境中替换环境变量。当前情况与此并无二致。

          最后,若你对 Next.js 有基本认知,就会发现存在多种机制可将服务器端数据(而非静态渲染数据)传递至客户端组件。例如可在服务器端组件中读取环境变量,再作为 props 传递给客户端组件。

          1. 所有环境变量都可能影响静态构建输出。例如,若API端点指向测试环境API,而页面基于该数据进行静态构建,那么——瞧!你的私有环境变量已导致公开构建版本无法部署至测试环境。

            我将直接屏蔽你。我在这行干了15年多,在多家上市公司担任过最高级别的IC职位,拿过数十万美元薪酬,还是/r/nextjs社区的顶级贡献者——可不是为了在Reddit上和自以为用“白痴”称呼别人很合适的人无谓争论。

    6. 依我看,这足以成为放弃Next.js用于任何生产环境的决定性因素。

      我理解这在Page Router中并非如此?

      1. 不,那里同样存在问题。静态渲染页面和ISR页面(含预缓存版本)都受此问题影响。更糟的是,公共环境变量会被内联到构建文件中。

    7. 哇,庆幸自己从未接触过Next.js。这简直是疯狂的设计决策。

    8. 他们强迫你用React做后端应用,却又不允许像配置后端应用那样配置它。真是绝了。

  3. 即便忽略糟糕的项目所有者,这篇从技术角度剖析Next缺陷的文章写得极好。我认为它本质上只是推销Vercel服务的手段,堪称“供应商锁定即服务”。

    (已修正错误表述)

    1. 在GCP或类似平台部署Next.js而非Vercel时,会失去哪些关键功能?我们正在这样操作,想确认是否遗漏了重要特性。

      1. 提醒得很及时——我之前评论时基于过时信息,经核实只要平台支持Node即可。现在限制基本只针对静态部署,这很合理,因为Next的大部分魔力在于后端(同链接页面下方)

        1. 在Node中部署也有诸多弊端,涉及中间件和所有缓存层。相比其他元框架,你基本只能面对一个黑盒子。

        2. 我之前评论时知识过时了,确实有误

          我没读过你最初的错误帖子,但感谢你勇于承认并根据新事实/知识进行修正。Reddit需要更多这样的态度,尤其是我本人。

        3. 借助Node支持,Next在Vercel上运行良好;真正的陷阱在于纯静态托管会丢失ISR、中间件和图像优化功能。

          在GCP上,请通过Cloud Run或App Engine运行Node服务器;为/_next/image启用Cloud CDN并设置缓存头;固定最小实例数以减少冷启动;导出时仅将GCS用于静态资源。若需边缘化中间件,可部署Cloudflare Workers或Fastly Compute作为前端。我在Render和Fly.io上运行过类似方案;配合Next快速搭建数据库到REST框架时,DreamFactory颇有帮助。

          所以,若想体验Next的魔力,请选择Node主机并避免纯静态托管。

      2. 对我而言,供应商锁定在于你编写的React代码依赖于Next.js的“魔法”——本质上若出现更优方案,迁移过程将非常痛苦。

        而使用Vite时,我感觉并未添加太多Vite特有的冗余代码。

        1. 不过这属于框架层面的问题。值得庆幸的是,代码层面其实没什么Vercel特有的限制。

        2. 我主要依赖的“Next.js魔法”是React服务器组件(严格来说是React标准组件,但目前似乎只有Next.js支持),我确实挺喜欢这个特性;还有React服务器操作(同理),不过我很快会停用它——相比常规API,它缺乏输入验证和版本控制功能实在令人不快。

          总的来说我绝非NextJS拥趸,我厌恶JS/TS,仅因业务需求被迫使用——要么给我Haskell→WASM,要么给我死亡。

          1. 除了NextJS之外,真有人在用RSC吗?

            我认为他们采用的指令模型简直愚蠢至极。谁会觉得文件开头的魔术字符串是个好主意?这是什么世界?

            1. Tanstack和React Router正在其框架式解决方案中构建支持。RedwoodJS似乎早已支持,但多年来他们的产品线始终未能获得任何实质性进展。

      3. 主要在于易用性。

        缓存是个大问题——默认缓存基于文件系统,若使用Docker化部署则无法共享。解决方案要么配置共享卷(类似Vercel的幕后实现),要么开发自定义后端(我最终采用的方案)。但官方文档都只是敷衍地表示“可以实现,祝你好运”。

        他们对(公共)环境变量的处理方式,本质上是在构建时硬编码,这种做法或许能勉强适应其构建流程。但若需在不同环境中部署同一Docker镜像,就必须实现自定义处理方案,而现有主流方法均不够完善(或无法在重大版本更新中持续稳定运行)。

        除此之外,NextJS的所有功能现已在“独立部署模式”中全面支持。只不过在Vercel上部署所有内容显然更为便捷。

        1. 你的选择是配置共享卷(NextJS 后台实现的方案)或实现自定义后端(我最终采用的方案)。两种方案的文档都归结为“可以实现,祝你好运”。

          我确信Redis可用于缓存。但实际部署时我不得不采用共享卷方案,因为需求团队使用的版本尚未支持Redis缓存。

          当然,若需缓存大型对象,Redis缓存的优势便不复存在。

      4. 对99%的使用场景毫无价值哈哈。据说middleware.ts仅在Vercel托管时才能在“边缘”运行,某些场景下能带来微弱性能提升。这种供应商锁定的狗屁理论在Reddit流传多年,而我们机构用Next.js已经七年了,经典托管服务和GCP都用得挺好。

        1. 当然,从技术上讲你可以自己部署,但如果你说在Vercel上使用NextJS和不用NextJS的区别仅仅在于边缘层的中间件,那你就是在耍人。你认真的吗?

          1. 还有别的选择吗?除了新手友好性之外,GCP似乎也提供同样的垃圾功能。

            1. 三年前我决定彻底放弃NextJS时的痛点:

              • 结构化日志记录基本不可能实现

              • 运行时环境变量需要在Dockerfile入口点搞黑科技

              • ISR与第三方CDN等服务根本无法兼容

              • 部署到Vercel时图片会自动优化

              • 草稿模式/预览环境

              https://vercel.com/docs/frameworks/full-stack/nextjs?framework=nextjs-app

              并非说这些问题在没有Vercel的情况下无法解决,只是使用Vercel会轻松得多。仅此而已。

            2. 现在Cloudflare Worker配置也支持了

    2. JavaScript生态中似乎正出现“现代”前端框架普遍面临此类问题的趋势。

    3. 我对这种分析的质疑在于:Vercel显然投入大量时间和资金与所有竞争对手合作,共同制定更易达成的部署标准(因竞争对手抱怨其原有标准过于严苛),从而实现与自身服务的特性对等。

      这或许源于同行压力,但恰恰与供应商锁定背道而驰。

  4. 我完全认同。其架构设计实在令人费解,宛如西部荒野般混乱,最终只能选择“管用就行”的方案。

  5. 更别提他们通过收购React维护者,实质上买通了React核心开发权,以此强行向所有人推销Next.js的“毒瘤”,借此兜售更多“无服务器”服务器。

      1. 我只查到他见过以色列总理。大概就这些了。可能不适合在这个论坛讨论。

  6. 我主要不满的是NextJS号称框架,但 __ 它真的算框架吗?__ 依我看,不过是几个功能模块用胶带拼凑而成,即便做个简单功能项目仍需依赖其他库。

    企业钟爱框架是因为它能标准化工作流程(不同项目无需处理不同库)。在这方面NextJS接近标准,但仍显不足。

  7. 他们似乎戴着理想主义眼镜专注于RSC模型,却忽略了实际需求。

  8. 文章写得极好。虽非政治立场,但我想尝试Nextjs开发时,发现其复杂度远超预期。至今它仍是史上最复杂的前端框架。那种强迫所有操作在服务器端处理的思维模式,加上客户端与服务器端独立的路由器设计,某些函数无法在客户端使用,最终只能自定义接口与后端交互?Vercel手握海量资源和资金,完全可以借鉴SvelteKit或Nuxt的方案。更令人反感的是他们赞助了两个替代方案,我担心这些工具会受到其影响而丧失开放性。Next在前端领域真的糟糕透顶。

    1. 作为Web框架,我认为它相当复杂。这话出自一个从C++/MFC/Windows起步的程序员之口。我曾尝试用Django和NextJS分别搭建“简单”网站(此前从未接触过这两者)。Django让我周末就能上线运行,而NextJS却耗费我数周才完成部署。话虽如此,我最终选择了NextJS,因为Cloudflare提供免费托管。附上我创建的网站链接:https://mybadstats.com/。这个网站纯粹是为了学习框架,为未来项目做准备,但过程挺有趣,所以我注册了域名并会持续更新。

      1. 看起来很酷。能透露用的是什么数据库吗?

    2. 复杂?如果你觉得NextJS复杂,那祝你好运去用其他框架开发吧,更别提实现独立后端了哈哈

    1. Vite及其衍生框架,比如Remix、Tanstack、Nuxt等

      1. Tanstack

        尚未达到生产就绪状态

        1. 它已经进入候选版本了,基本功能基本齐备

          1. 事实并非如此。

            就连他们提供的认证示例在生产环境中都存在缺陷(除非你接受每个路由都要重复验证认证凭证N次)。

            此外,最新版本中,当 node_modules 位于父目录时,单仓库项目会因某些原因出现奇怪的依赖问题而崩溃。而之前版本是正常的。

            还有更多类似的怪癖问题…

            1. 没错,这只是候选版本,尚未达到 1.0 标准。对你而言很重要,但并非人人都需要。我认为它足够热门值得列出

            2. 所有这些极其恼人的缺陷都存在于RC版中

              仅仅因为你将软件版本标记为“候选发布版”甚至“1.0”,并不意味着它已具备生产环境部署条件。除非你是业余爱好者,能容忍所有这些怪癖出现。

        2. NextJS当年强迫我们吞下实验性功能时也一样。

    2. 任何需求都适用。

      根据我的经验,最佳选择取决于内容和部署需求:Astro或Vue/Nuxt。

    3. 一如既往,关键在于构建对象。若是50人使用的后端系统,直接用HTML+CSS加标准HTTP即可。全部服务器端渲染。

      90%的软件根本不需要Facebook级别的复杂度。

    4. TanStack Start前景可期。它具备Next的类似特性,但类型安全更强且魔术代码更少。

    5. React Router v7。稳定可靠、类型安全、基于Vite构建,且足够灵活——无论你想实现SPA的CSR、SSR、RSC还是任何中间方案,它都不会阻碍你。

      1. React Router既是框架,亦是库,更是攻击直升机。

        坦白说上周发布的TanStack Start让我期待它能迅速普及,终结所有无谓的争论

  9. 我深有同感。

    距我上次使用nextjs(app-router出现前)已有数年,当时我特别注意避免应用与nextjs耦合——那时候还做得到。

    我们采用单文件路由器渲染基于React Router的应用,该应用通过mui实现全主题化。

    我们还利用nx将应用迁移为库文件,分别导入NextJS和Vite应用进行独立部署。可见当时完全可以这样使用NextJS。如今有了app文件夹和服务器组件,这种做法可能行不通了——这也是我不再使用Next的原因之一。

  10. 嗯…基于文件的路由还行?

    我们之所以弃用它,是因为这已不是90年代,我们拥有更优秀的软件。

    若你不知晓CGI-bin,不妨查查——那简直糟糕透顶。

    更别提它牺牲了可搜索性、文件名里那些诡异的括号,以及强加的隐含逻辑,只为换取所谓“酷炫外观”——作者自以为聪明,实则不懂编程历史。

  11. AI垃圾图。往下拉。弹窗。拜拜!

  12. 只要它继续作为众多SaaS供应商SDK的框架首选,这些问题都无关紧要。

  13. 这是极其宝贵的反馈,也是我首次见到针对NextJS的深度批判(指超越“我因没看清文档产生单点痛点”这类评论,或者…嗯,本帖部分内容的讨论)。

    但问题是,现有方案真有更优选择吗?有人鼓吹只需htmx就够了,这简直脱离现实需求。人们选择NextJS正是因为它能解决实际问题——毕竟大家开发的是真正在运行的应用,不是为了漂亮GitHub页面而搞的业余项目。

  14. 该死,又是每月一次的新JavaScript框架狂欢季

  15. 但它的崛起酷似面向对象编程的繁荣期:快速普及、狂热追捧,以及对其魔力的近乎盲目的信仰。

    这句话让我质疑整篇帖子的可信度。

    若不用对象,测试中如何隔离和替换副作用?

    无论某些群体如何诋毁面向对象编程,它从未经历过兴衰周期,也非昙花一现的炒作。它始终稳居行业标准之列。即便在依赖函数式编程的语言或软件中,必要时仍会采用面向对象技术——因为认为二者非此即彼的选择本身就是错误的,它们并非互斥关系。

  16. 我完全赞同认真探讨Next在某些场景下为何变得极其复杂,其部分特性和对边缘功能的恼人执着纯粹是为了支撑平台运行等问题,但…

    天啊 这帖子里充斥着大量错误信息。说实话,这篇文章写得极其糟糕,还摆出一些极其可疑的稻草人论证。作者先设定一些奇怪的假设,比如“你应该能随意更换打包工具”(谁说的???)然后反驳这些假设。

    接着出现这样的论断:

    这些机制是隐式的、由编译器驱动的,而非应用驱动的。

    呃…啥意思?

    这些神奇的入口点永远无法良好组合。长远来看,分析这类系统绝非易事。基于文件系统的路由机制勉强可用,但仍易引发冲突和错误。

    哇,精彩的论点。可以接受?但容易出错?行吧,能稍微展开下吗?

    是的,这可能是我技术水平的问题,但框架难道不该防止我犯错,或者至少让操作变得显而易见且枯燥无味吗?

    我的意思是…也许?某种程度上?编程本身就复杂,当你接触到功能更强大的东西时——它能让你用不同方式实现更多功能(服务器组件、静态编译、中断服务程序、流处理等等)——你不可能凭空消除所有复杂性。要做到这点,只能替用户做出选择,从而降低灵活性。这基本上是所有抽象机制的运作原理。越简单,就越缺乏灵活性。

    懒得细说了,但说实话这不过是用户们零散抱怨的杂烩。很多抱怨我都能理解,但坦白讲:X类产品只有两种——没人用的,和人人抱怨的。

  17. 糟透了,居然用Medium平台。

  18. 这文章写作者根本没搞过像nextjs这么复杂的东西,却摆出一副“换我负责就能做得更好”的架势。

    拿证据来。真有本事就证明给你看——去造个新元框架,搞自己的打包工具,建专属托管平台,开发专属单仓库工具链,证明你能做得更棒更简洁。

  19. 一年后:
    为何Astro未能达到软件工程标准

  20. 几乎所有论点都围绕React展开…看来你也不懂如何编写软件🏊🏊

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: