使用 Netlify 和 Next.js 分解庞大的构建

已发表: 2022-03-10
快速总结 ↬静态生成非常适合性能——直到应用程序变得太大并且构建时间过长。 今天,我们将看看 Netlify 的新按需构建器如何解决这个问题。 此外,我们将它与 Next.js 的增量静态再生相结合,以获得最佳的用户和开发人员体验。 当然,还要对这些结果进行基准测试!

使用静态生成的网站的最大痛苦之一是随着应用程序的增长,构建速度会越来越慢。 这是任何堆栈在某些时候都面临的不可避免的问题,并且根据您使用的产品类型,它可能会从不同的角度发生。

例如,如果您的应用在生成部署工件时有多个页面(视图、路由),则这些路由中的每一个都将成为一个文件。 然后,一旦达到数千人,您就会开始想知道何时可以部署而无需提前计划。 这种情况在电子商务平台或博客上很常见,它们已经是网络的很大一部分,但不是全部。 不过,路线并不是唯一可能的瓶颈。

一个资源密集型的应用程序也最终会到达这个转折点。 许多静态生成器进行资产优化以确保最佳的用户体验。 如果没有构建优化(增量构建、缓存,我们将很快实现),这最终也将变得难以管理——考虑遍历网站中的所有图像:一遍又一遍地调整大小、删除和/或创建新文件。 一旦所有这些都完成了:记住 Jamstack 从内容交付网络的边缘为我们的应用程序提供服务。 所以我们仍然需要将东西从编译它们的服务器移动到网络的边缘。

Jamstack 通用服务架构
Jamstack 通用服务架构(大预览)

除此之外,还有另一个事实:数据通常是动态的,这意味着当我们构建和部署应用程序时,可能需要几秒钟、几分钟甚至一个小时。 与此同时,世界一直在旋转,如果我们从其他地方获取数据,我们的应用程序肯定会过时。 不能接受! 再次构建以更新!

构建一次,需要时更新

一段时间以来,基本上每个 Jamstack 平台、框架或服务都将解决庞大的构建问题放在首位。 许多解决方案都围绕增量构建。 实际上,这意味着构建将与它们与当前部署的差异一样庞大。

不过,定义差异算法并非易事。 为了让最终用户真正受益于这种改进,必须考虑缓存失效策略。 长话短说:我们不想让没有改变的页面或资产的缓存失效。

Next.js 提出了增量静态再生 ( ISR )。 本质上,它是一种为每条路由声明我们希望它重建的频率的方法。 在底层,它简化了服务器端的大量工作。 因为每条路由(无论是否动态)都会在特定的时间范围内重建自己,并且它完全符合 Jamstack 公理,即每次构建时都使缓存失效。 将其视为max-age标头,但用于 Next.js 应用程序中的路由。

要启动您的应用程序,ISR 只需一个配置属性即可。 在您的路由组件上(在/pages目录内)转到您的getStaticProps方法并将revalidate键添加到返回对象:

 export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }

上面的代码片段将确保我的页面每小时重建一次并获取更多的口袋妖怪来显示。

我们仍然时不时地获得批量构建(在发布新部署时)。 但这允许我们将内容与代码分离,通过将内容移动到内容管理系统(CMS),我们可以在几秒钟内更新信息,无论我们的应用程序有多大。 告别用于更新拼写错误的 webhook!

按需构建器

Netlify 最近推出了 On-Demand Builders,这是他们支持 Next.js 的 ISR 的方法,但也适用于包括 Eleventy 和 Nuxt 在内的框架。 在上一届会议中,我们确定 ISR 是朝着缩短构建时间迈出的一大步,并解决了很大一部分用例。 尽管如此,还是有一些警告:

  1. 基于持续部署的完整构建。
    增量阶段仅部署之后和数据发生。 不可能以增量方式交付代码
  2. 增量构建是时间的产物。
    缓存按时间失效。 因此,可能会发生不必要的构建,或者所需的更新可能需要更长的时间,具体取决于代码中设置的重新验证期。

Netlify 的新部署基础架构允许开发人员创建逻辑来确定他们的应用程序的哪些部分将基于部署构建以及哪些部分将被推迟(以及它们将如何被推迟)。

  • 危急
    无需采取任何行动。 您部署的所有内容都将基于push构建。
  • 延期
    应用程序的特定部分不会在部署时构建,它将在第一个请求发生时延迟到按需构建,然后它将作为其类型的任何其他资源进行缓存。

创建按需构建器

首先,将 netlify/functions 包作为devDependency到您的项目中:

 yarn add -D @netlify/functions

完成后,它就像创建一个新的 Netlify 函数一样。 如果您没有为它们设置特定目录,请前往netlify/functions/并为您的构建器创建一个任意名称的文件。

 import type { Handler } from '@netlify/functions' import { builder } from '@netlify/functions' const myHandler: Handler = async (event, context) => { return { statusCode: 200, body: JSON.stringify({ message: 'Built on-demand! ' }), } } export const handler = builder(myHandler)

从上面的代码片段可以看出,按需构建器与常规的 Netlify 函数分开,因为它将其处理程序包装在builder()方法中。 此方法将我们的功能连接到构建任务。 这就是您只需要在必要时才延迟构建应用程序所需的全部内容。 从一开始就进行小型增量构建!

Netlify 上的 Next.js

要在 Netlify 上构建 Next.js 应用程序,应该添加 2 个重要的插件,以获得更好的体验:Netlify Plugin Cache Next.js 和 Essential Next-on-Netlify。 前者更有效地缓存您的 NextJS,您需要自己添加它,而后者对 Next.js 架构的构建方式进行了一些细微调整,因此它更适合 Netlify,并且默认情况下可用于 Netlify 可以识别的每个新项目是使用 Next.js。

使用 Next.js 的按需构建器

构建性能、部署性能、缓存、开发人员体验。 这些都是非常重要的主题,但数量很多——并且需要时间来正确设置。 然后我们开始讨论关于专注于开发人员体验而不是用户体验的旧讨论。 这是事情进入积压中的隐藏位置以被遗忘的时间。 并不真地。

Netlify 得到了您的支持。 只需几个步骤,我们就可以在 Next.js 应用程序中利用 Jamstack 的全部功能。 现在是时候卷起袖子,把它们放在一起了。

定义预渲染路径

如果您之前在 Next.js 中使用过静态生成,您可能听说过getStaticPaths方法。 此方法适用于动态路由(将呈现各种页面的页面模板)。 无需过多关注此方法的复杂性,重要的是要注意返回类型是具有 2 个键的对象,就像在我们的概念验证中,这将是 [Pokemon] 动态路由文件:

 export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
  • paths是一个array ,执行与该路径匹配的所有路径,这些路径将被预渲染
  • fallback有 3 个可能的值:blocking、 truefalse

在我们的例子中,我们的getStaticPaths正在确定:

  1. 不会预渲染任何路径;
  2. 每当调用此路由时,我们都不会提供备用模板,我们将按需呈现页面并让用户等待,从而阻止应用程序执行任何其他操作。

使用 On-Demand Builders 时,请确保您的后备策略符合您应用程序的目标,Next.js 官方文档:后备文档非常有用。

在 On-Demand Builders 之前,我们的getStaticPaths略有不同:

 export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }

我们正在收集我们打算拥有的所有 pokemon 页面的列表,将所有pokemon对象映射到一个带有 pokemon 名称的string ,然后转发返回带有它的{ params }对象到getStaticProps 。 我们的fallback设置为false ,因为如果路由不匹配,我们希望 Next.js 抛出404: Not Found页面。

您可以检查部署到 Netlify 的两个版本:

  • 使用 On-Demand Builder:代码,实时
  • 完全静态生成:代码,实时

该代码也在 Github 上开源,您可以轻松地自行部署以检查构建时间。 有了这个队列,我们​​进入下一个主题。

构建时间

如上所述,前面的演示实际上是一个概念验证,如果我们无法衡量,没有什么是真正的好或坏。 为了我们的小研究,我去了 PokeAPI 并决定捕捉所有的口袋妖怪。

出于可重复性的目的,我限制了我们的请求(到1000 )。 这些实际上并不全部在 API 中,但它强制所有构建的页面数量相同,无论内容是否在任何时间点得到更新。

 export const fetchPkmList = async () => { const resp = await fetch(`${API}pokemon?limit=${LIMIT}`) const { count, results, }: { count: number results: { name: string url: string }[] } = await resp.json() return { count, pokemons: results, limit: LIMIT, } }

然后在单独的分支中将两个版本发射到 Netlify,这要归功于预览部署,它们可以在基本相同的环境中共存。 为了真正评估两种方法之间的差异,ODB 方法非常极端,没有为该动态路由预渲染任何页面。 虽然不推荐用于实际场景(您将希望预渲染流量大的路线),但它清楚地标志着我们可以使用这种方法实现的构建时性能改进的范围。

战略页数资产数量构建时间总部署时间
完全静态生成1002 1005 2分32秒4分15秒
按需构建器2 0 52 秒52 秒

我们的小 PokeDex 应用程序中的页面非常小,图像资源非常精简,但部署时间的收益非常显着。 如果一个应用有中到大量的路由,绝对值得考虑ODB策略。

它使您的部署更快,因此更可靠。 性能下降仅发生在第一个请求上,从后续请求开始,渲染的页面将被缓存在边缘,使得性能与完全静态生成的性能完全相同。

未来:分布式持久渲染

就在同一天,按需构建器被宣布并提前访问,Netlify 还发布了他们对分布式持久渲染 (DPR) 的评论请求。

DPR 是 On-Demand Builders 的下一步。 它利用这种异步构建步骤,然后缓存资产直到它们实际更新,从而利用更快的构建。 不再为 10k 页面的网站构建完整版本。 DPR 使开发人员能够通过可靠的缓存和使用按需构建器来完全控制构建和部署系统。

想象一下这个场景:一个电子商务网站有 10k 个产品页面,这意味着构建整个应用程序进行部署大约需要 2 个小时。 我们不需要争论这是多么痛苦。

使用 DPR,我们可以在每次部署时设置前 500 个页面。 我们最繁忙的流量页面始终为我们的用户准备好。 但是,我们是一家商店,即每一秒都很重要。 所以对于其他 9500 个页面,我们可以设置一个构建后挂钩来触发它们的构建器——异步部署我们剩余的页面并立即缓存。 没有用户受到伤害,我们的网站以最快的速度更新,缓存中不存在的所有其他内容都被存储了。

结论

尽管本文中的许多讨论点都是概念性的,并且有待定义实现,但我对 Jamstack 的未来感到兴奋。 作为一个社区,我们正在取得的进步围绕最终用户体验展开。

您对分布式持久渲染有何看法? 您是否在应用程序中尝试过 On-Demand Builders? 在评论中让我知道更多信息或在 Twitter 上给我打电话。 我真的很好奇!

参考

  • “使用 Next.js 进行增量静态再生 (ISR) 的完整指南”,Lee Robinson
  • “使用按需构建器在 Netlify 上更快地构建大型站点”,Asavari Tayal,Netlify 博客
  • “分布式持久渲染:一种用于更快构建的新 Jamstack 方法,”Netlify 博客的 Matt Biilmann
  • “分布式持久渲染 (DPR)”,Cassidy Williams,GitHub