Next.js 真有那么好用吗?Netlify 谈他们遇到的六个现实问题

360影视 动漫周边 2025-05-06 15:36 1

摘要:实际上,我们在这篇文章上花费了很多的时间,思考如何以最好的方式呈现下面的事实和建议,同时又能避免让它显得是一篇攻击性文章。毕竟,我们与 Vercel 在平台领域存在竞争关系。

作者 | Philippe Serhal & Elad Rosenheim

译者 | 张卫滨

策划 | Tina

Netlify 部署了数十万个 Next.js 网站,真正的挑战也随之而来。本文最初发表于 Netlify 的博客网站,由 InfoQ 中文站翻译分享。

关于此刻的说明

实际上,我们在这篇文章上花费了很多的时间,思考如何以最好的方式呈现下面的事实和建议,同时又能避免让它显得是一篇攻击性文章。毕竟,我们与 Vercel 在平台领域存在竞争关系。

最近,围绕 Next.js 中间件发生了一起安全事件,在社交网络上引发了一场小型的风波,并招致了对意外事件处理方式的批评。

这篇文章并不是关于这一安全事件的。相反,本文关注的是当前更大的背景,也就是确保 Next.js 按预期方式为客户运行所面临挑战。其中许多挑战(也可以说是意外事件响应)都与 Next.js 维护方式的封闭性有关。但现在每个人都在真诚努力地解决这个问题。

在此,我们的目的是更好地了解所面临的挑战,并为今后的协作规划出一条道路。

重新介绍 Next.js

Next.js 是一个开源的 Web 开发框架,由提供 Next.js 托管服务的云提供商 Vercel 创建和管理。

该框架在 React 的基础上进行了创新,使其成为许多开发人员的首选工具,开发人员这样做是有充分理由的,Next.js 引入了对 SSR、SSG、ISR 和 API 路由等概念的内置支持。

在 Netlify,我们很自豪能提供全面的 Next.js 支持,确保开发人员在不牺牲功能的情况下选择部署方案。然而,保持对 Next.js 这种级别的支持是有代价的,并且会带来独特的工程挑战。这些挑战不仅局限于 Netlify,还包括 Cloudflare、AWS Amplify Hosting、SST、Google Firebase App Hosting 和 Microsoft Azure Static Web Apps 等同行。

现在,我们来看看 Netlify 的工程师为保持与 Vercel 平台特性对等所做的幕后工作,以及我们为跟上 Next.js 不断变化的节奏所做的工作。

挑战 1:没有适配器支持

开源软件的一大优势在于其可移植性,开发人员和组织应该可以在不同的提供商之间自由转移,而不必担心被某个特定的供应商锁定。例如,npm CLI 既集成了 npm 公司的注册表,也集成了所有第三方的注册表,Docker 也集成了 DockerHub,这样的例子不胜枚举。

为了实现这一目标,大多数现代 web 开发框架都使用了适配器、插件或预设(preset)的概念,以便根据特定的部署目标调整框架的输出。提供商需要采用这种方式来提供和配置基础设施,以便于为应用程序提供动力。例如,Remix、Astro、SvelteKit、Gatsby 和 Qwik。

为可替换的构建适配器提供一流支持的框架(Astro),每个适配器都面向特定的部署环境或提供商

有些框架甚至更进一步,Nuxt、Analog、SolidStart 和 TanStack Start 都使用相同的底层机制(Nitro),因此可以共享部署目标预设。这种通用基础的预期工具和范围是一个快速发展的话题,不同的意见在公开场合争论不休,这也是很正常的事情。

基于 Nitro 的框架对可交换的构建适配器具有一流的支持,但更进一步的是共享它们的适配器

这些模式允许前端开发人员保持代码核心不变,如果决定部署到另一个提供商,只需替换适配器即可。

这些适配器可以由框架作者、托管服务提供商、社区或上述各方共同维护。框架通常具有良好的结构,这样任何人都能构建自己的适配器,以防他们所选择的提供商没有对应的适配器。之所以能做到这一点,是因为它们有公开的适配器规范文档。这也意味着意外情况会更少,因为适配器接口的更改会遵循语义化版本的约定。

在平台供应商方面,他们通常有自己的 API 文档,说明框架应如何与平台交互,任何框架都可以使用这些 API。在我们的场景中,这就是 Netlify Framework API。

以 Netlify 如何使用带有 Netlify 构建适配器的框架 (Astro) 来构建站点为例

Next.js 所面临的独特挑战在于,尽管 Vercel(平台)早在 2022 年就为框架提供了构建输出 API(Build Output API),但 Next.js 本身并不遵循该 API,也没有适配器机制,任何其他角色都无法通过该机制支持其他平台。相反,Next.js 构建使用的是一种私有的、基本没有文档记录的格式,这种格式可能会发生变化。

相反,Netlify、Cloudflare、AWS Amplify Hosting、SST、Google Firebase App Hosting 和 Microsoft Azure Static Web Apps 等提供商必须从磁盘读取 Vercel 定制的、部分未文档化的构建输出,将其转换为自己的格式,然后再写回磁盘上。

Next.js 构建仅与 Vercel 兼容,因此其他平台(如 Netlify)必须在事后将其转换为自己的格式。

在 Netlify,我们会在 Next.js 构建后自动运行一个构建插件(OpenNext Netlify 适配器)。再加上全面的自动化测试(包括框架自身的测试和我们自己的测试套件),最终在 Netlify 实现了强大的 Next.js 体验。对于绝大多数站点来说,都可以做到开箱即用,无需任何配置。但是,如果 Next.js 能够遵循既定的适配器模式,实现起来会更简单、更易于维护,也更便于社区为其做出贡献。这个问题已经断断续续讨论了一段时间,但自从 OpenNext 小组的范围扩大,明确表达了多方的需求后,这个问题才真正得到解决。现在,它 终于启动了!

挑战 2:没有针对无服务器部署的生产级文档

Next.js 部署文档列出了这些部署选项:

你可以使用 Vercel 部署托管的 Next.js,也可以在 Node.js 服务器、Docker 镜像甚至静态 HTML 文件上自行托管。

这里的“Node.js 服务器”指的就是一个简单的 Node.js 服务器。对于正式的项目来说,Node.js 服务器的单个唯一实例(无论是否使用 Docker)不具备横向扩展的能力和零停机部署的能力,这并不是一种可行的部署策略,而且完全静态的网站目前只能覆盖有限的 Next.js 使用场景。在默认的情况下,这并不能支撑 Next.js 的大部分功能,比如,边缘中间件、全局持久化页面、获取缓存以实现增量式静态再生(Incremental Static Regeneration)和按需重新验证等,正是这些特性使得 Next.js 在大规模环境中变得非常强大。

自托管文档 简要介绍了一些复杂问题:

缓存和重新验证页面(使用增量静态再生(ISR)或 App Router 中较新的功能)使用相同的共享缓存。[...]默认情况下,生成的缓存资产将存储在内存(默认为 50MB)和磁盘中。如果使用 Kubernetes 等容器编排平台托管 Next.js,每个 pod 都将拥有一份缓存。由于默认情况下 pod 之间不共享缓存,为防止显示陈旧的数据,可以配置 Next.js 缓存以提供缓存处理器并禁用内存缓存。

陈旧数据的问题比想象中更棘手。例如,由于每个节点都有自己的缓存,如果在服务器操作或路由处理器代码中使用 revalidatePath,该代码将仅在处理该操作 / 路由的某个节点上运行,并且仅清除该节点的缓存。

文档提供了如何通过自定义 CacheHandler 来实现这一功能的概述。自定义缓存处理器必须要关注多种类型的缓存条目(比如,页面、路由、抓取、图像、重定向),并为每种缓存条目制定单独的逻辑,将缓存键与后备存储进行规范化,处理按需触发的重新验证,处理缓存标签,解析和处理各种未文档化的构建清单文件,处理大量极端情况等等。

由于 Next.js 是一个被广泛采用的重要框架,我们致力于为其提供全面的支持,这意味着专门负责平台框架支持的工程团队已经积累了必要的知识,并实现了自己的缓存处理器和相关的测试。如果即将推出的金丝雀版本有什么变化,我们会及时发现。

上述挑战意味着,团队很容易就会把过多的时间花在 Next.js 上。幸运的是,我们尽可能使用自己的 平台原语(platform primitive),并在强大功能的基础上进行构建(在必要时进行迭代),而不是为每一个需求均量身定制特定框架的解决方案。

你应该也能做到这一点

即便尽可能使用我们自己的现有功能,但 Netlify 的缓存处理器也需要约 500 行代码和约 500 行测试,这并不是一件容易完成的任务。对于大多数考虑自托管的开发人员来说,这种类型的投资就超出他们的能力范围了。

由于 Next.js 对这些运行时细节进行了抽象,因此急需扩展适配器的概念,使其不仅能够支持构建时的问题。例如,也许应该重新设计平台的 CacheHandler 接口,以便在平台侧要求最少的框架逻辑(随时可能发生变化)。

目前,这些内容超出了 OpenNext 小组和 Vercel 讨论的构建时适配器的范围,但我们乐观地认为,在共享论坛上加强合作将为 Next.js 开发人员带来更好、更轻松、更简单、更加文档化的部署方案。

挑战 3:未文档化的行为

Next.js 框架包含 大量未文档化的选项、功能和行为。为使平台与 Vercel 实现功能对等,平台供应商目前需要了解这些情况并将其考虑在内。

例如,部署到 Vercel 的 Next.js 站点会使用自己独特的 Next.js 代码路径,并通过未文档化的 minimalMode 进行选择。这样就禁用了该框架的许多核心功能,而这些功能可能会在 Vercel 平台(自然是闭源的)中重新实现。虽然 Netlify 没有这样做,但一些供应商甚至选择利用这种未文档化的模式,并在自己的平台中反向实现了同等的功能。

我们的方法:主动的自动化测试

我们的 Next.js 适配器包含数百个测试,这些测试能够在 Linux、macOS 和 Windows 上针对 Next.js 13、14、15 和最新的金丝雀版本运行。其中大部分是集成测试,用于构建和运行 Next.js 站点,还有很多是端到端测试,用于将测试站点部署到 Netlify。

当然,这些只是入门级的测试。此外,对于所测试的每个 Next.js 版本,我们还克隆了源 Next.js Git 仓库,并运行其 1700 多个端到端的集成测试,调整后部署到 Netlify。这些测试设计的范围非常广泛,而且通过在浏览器中像用户一样运行 Playwright 测试,能够对整个真实体验进行测试,这让我们对发布变更非常有信心。

我们每天都在运行这套测试,并生成一份报告(可公开获取),其中自动包含我们在 GitHub 上跟踪的已知问题的评注,并主动通知我们的框架团队新的未知问题。

这一点尤为重要,因为它不仅针对稳定的 Next.js 版本进行测试,还针对“金丝雀”预发布版本进行测试。通过匹配最新的“金丝雀”版本的测试,我们就能确保在新功能发布和普遍可用之前,逐步增加对新功能的支持,并处理即将到来的破坏性更改(对文档化和未文档化接口的更改)。由于缺乏路线图的透明度和发布的可预测性(见下文第 5 和第 6 项挑战),我们确实有必要这样做。

挑战 4:不是建立在开放 Web 标准之上

在撰写本文时,Remix 框架的网站 上明确写着“专注于网络标准”,Astro 的网站 的宣传语是“零锁定”,SvelteKit 网站则要求你“学习跨环境工作的网络标准”。

使用增量静态再生(ISR)时,Next.js 会带有一个 Cache-Control 响应头,其中包含不合法的 Stale-While-Revalidate (SWR) 指令,如:

Cache-Control: s-maxage=600, stale-while-revalidate

而 RFC 5861 要求使用 Time-To-Live 值,例如:Cache-Control: s-maxage=600, stale-while-revalidate=60

这看起来可能只是一个烦人的小问题,也许根本不值得在此提及。但这一细节的影响在于,许多 CDN 会默默地忽略不合法的指令。供应商必须首先发现问题的存在,然后要么为了 Next.js 而选择支持不合法的头信息,要么在其 Next.js 胶水代码中加入转换头信息的代码。虽然现在可以选择默认使用合法的头信息(参见 GitHub #52251、#61330、#65867 和 #65887),但这个问题最终还是以默认返回非标准的头信息而 告终。这只是众多问题中的一个样例而已。

此外,Next.js 的某些功能(如 ISR)是通过与 Vercel 平台功能深度集成的特殊解决方案来实现的。在许多情况下,这些功能也可以建立在开放 Web 标准(如标准 Web API)之上,以实现更大的可移植性。Kent C. Dodds 就是出于这种考虑,撰写了“为什么我不会使用 Next.js”这篇文章。

倡导开放的 Web

Netlify 的使命是共同构建一个更好的 Web。

我们通过参与 WinterCG(现为 WinterTC)等工作组和 OpenNext 等计划、为框架和整个社区起草 RFC、为合作伙伴提供稳定的文档化平台、随着 Web 的不断发展为新的平台标准和 API 提供支持,以及抓住一切机会倡导开放 web,努力将这一理念付诸实践。

但是,为了解决我们对 Next.js 的直接、具体需求,我们需要更多的东西:

原语而非框架

Kent Beck 说:“首先让改变变得简单(这可能很难),然后再进行简单的改变”。在某种程度上,这就是我们 去年的策略。我们的目标不是针对性地解决 Next.js 的每个新功能,而是先确定底层平台原语,使该功能易于采用,它不仅局限在 Next.js 中,而是在任何框架中,甚至在没有框架的情况下。

例如,我们没有在平台深处实现 Next.js 的增量静态再生(ISR),而是 完全通过简单的 HTTP 响应头实现了对标准 Stale-While-Revalidate 指令和其他高级缓存原语的支持。因此,在我们的 Next.js 适配器中,只需几行简单的代码就能实现 Next.js ISR,而且由于所有其他 SSR 框架都允许用户设置头信息,因此这些框架也能自动使用这一功能,甚至 无需任何框架。

挑战 5:缺乏路线图的可见性

大多数框架都会发布公开的路线图,让开发人员、提供商和集成开发人员及时了解即将推出的新功能、弃用的功能、破坏性变更等。例如,Astro、Remix、Qwik、Nuxt、Angular,以及 Vite 和 Svelte 的最小化路线图

Next.js 没有公开的路线图,也没有同样的透明度。

虽然开发人员可以自由选择何时升级自己的网站,但托管服务提供商和集成开发人员必须满足社区的期望,尽快为新版本提供支持。

此外,大多数框架的路线图都是与社区合作制定的。

Next.js 的治理模式规定,“大型架构决策和功能以征求意见(Request for Comments,RFC) 的形式启动”。然而,在撰写本文时,八年来只有四个社区 RFC 被采纳,其中只有一个非谷歌贡献者。构思、决策和路线图都是闭门造车,每年在发布新版本的同时,都会在 Vercel Conf 和 Next.js Conf 上与公众分享两次。

通过与社区(OpenNext 成员和其他成员)的合作,我们将致力于为 Next.js RFC 提出建议并做出贡献,以应对这些的挑战,例如上述的构建输出适配器。让我们携手共建!

挑战 6:缺乏发布的可预测性

大多数框架要么定期更新其公开路线图(见上文第 5 条),要么在取得进展时公开宣布即将发生的变更(如 Nuxt),要么遵守固定的发布时间表(如 Angular)。但是 Next.js 的情况并非如此。开发人员、托管提供商、集成开发人员和整个社区只能靠猜测以及字里行间的解读。

例如,Next.js 15 发布候选版于去年 5 月份发布,但从它发布后到 5 个月之后的下一个发布候选版(几天后就发布了 Next.js 15 稳定版)之间没有共享任何更新,只能通过查看 canary 分支的 2254 次提交来了解其更新情况。

我们是如何跟踪 Next.js 发行版和预发布版的

我们像鹰一样关注着 Next.js 的 PR 和发布。事实上,我们花了太多时间来做这件事,以至于我们构建了一个小型的服务来自动完成这项繁重的工作。

请放心,Cloudflare、AWS Amplify Hosting、SST、Google Firebase App Hosting、Microsoft Azure Static Web Apps 等公司的团队也在做同样的事情。

幸运的是,在 Vercel 的努力下,我们与 Next.js 团队建立了沟通渠道。有了这些,我们希望能更好地了解框架即将发生的变化。

展望未来

现在,总结一下我们的现状和采取的具体步骤:

首先,我们致力于与其他提供商、社区和 Next.js 团队合作。我们有一个共同的目标,那就是为 Next.js 开发人员及其站点访问者提供良好的体验。这也是我们与 SST 和 Cloudflare 团队一起 加入 OpenNext 计划 的原因。

其次,在 Vercel 工程师的努力下,我们已经与 Next.js 核心团队建立了直接的沟通渠道。我们乐观地认为,这将有助于我们从源头上应对挑战。

第三,行胜于言,根据明确定义的 Next.js 治理模式,我们将开始与其他提供商合作起草 RFC,以应对其中的一些挑战,目前这项工作已经开始。

我们期待着共同建设一个更美好的 Web。

声明:本文由 InfoQ 翻译,未经许可禁止转载。

今日好文推荐

OpenAI 黑科技 Deep Research 诞生记:一个工程师的“不务正业”如何改变 AI 战争格局

名校硕士AI造假面试现场“社死”!差点蒙混过关,因一个基本错误被识破,面试官:软件圈很小,好自为之

Redis之父宣布“Redis再次开源”!网友:骗我一次,算你狠;骗我两次,是我蠢

7B参数比 Kimina 72B 版更强!DeepSeek 新模型将“自动化所有运算”?奥赛生实测能力“太棒”

来源:InfoQ

相关推荐