优化在 Next.js 中加载第三方脚本

了解 Next.js 的 Script 组件背后的愿景,该组件提供了一种内置解决方案来优化第三方脚本的加载。

莉娜·索霍尼
Leena Sohoni
侯赛因·吉尔德
侯赛因·吉尔德

在来自移动设备和桌面设备上的网站的请求中,大约有 45% 是第三方请求,其中 33% 是脚本。第三方脚本的大小、延迟时间和加载速度都会严重影响网站的性能。Next.js Script 组件内置了最佳实践和默认设置,可帮助开发者在其应用中引入第三方脚本,同时解决潜在性能问题。

第三方脚本及其对性能的影响

第三方脚本可让网站开发者利用现有解决方案来实现常见功能并缩短开发时间。但是,这些脚本的创建者通常不会有动力考虑对使用方网站性能的影响。对于使用它们的开发者来说,这些脚本也会成为一个黑箱。

根据不同类别的第三方请求,脚本占网站下载的大量第三方字节。默认情况下,浏览器会根据脚本在文档中的位置确定其优先级,这可能导致对用户体验至关重要的脚本的发现或执行出现延迟。

布局所需的第三方库应尽早加载,以呈现网页。初始渲染不需要的第三方应该延迟,以免它们阻止主线程上的其他处理。Lighthouse 提供两项审核来标记阻塞渲染或主线程阻塞的脚本。

Lighthouse 审核报告消除了会阻塞渲染的资源并最大限度地减少第三方使用

请务必考虑网页的资源加载顺序,以确保关键资源不会延迟,并且非关键资源不会阻止关键资源。

虽然有最佳实践可以减少第三方的影响,但并非每个人都可能知道如何针对自己使用的每个第三方实施这些最佳实践。这个问题可能很复杂,原因如下:

  • 平均而言,网站会在移动设备和桌面设备上使用 21 到 23 个不同的第三方(包括脚本)。每种工具的用法和建议可能会有所不同。
  • 实现许多第三方可能会因是否使用特定框架或界面库而异。
  • 经常会引入较新的第三方库。
  • 与同一第三方相关的业务要求不尽相同,使得开发者很难实现标准化使用。

Aurora 专注于第三方脚本

Aurora 与开源 Web 框架和工具的协作包括提供强大的默认设置和专业工具,帮助开发者在性能、无障碍功能、安全性和移动端就绪性等方面改善用户体验。2021 年,我们专注于帮助框架堆栈改善用户体验及其核心网页指标指标。

为实现我们提高框架性能的目标,最重要的一步就是研究 Next.js 中第三方脚本的理想加载顺序。Next.js 等框架具有独特的优势,可提供实用的默认值和功能,帮助开发者高效加载资源(包括第三方资源)。我们研究了广泛的 HTTP Archive 和 Lighthouse 数据,以找出哪些第三方在不同的框架中阻止渲染最多。

为了解决主线程阻止应用中使用的第三方脚本的问题,我们构建了 Script 组件。该组件封装了排序功能,以便为开发者提供更好的控制第三方脚本加载。

对没有框架组件的第三方脚本进行排序

我们在提供的指南中提供了一些可降低阻止渲染的脚本影响的方法,其中包括以下方法来高效地加载第三方脚本及按顺序运行这些脚本:

  1. asyncdefer 属性与 <script> 标记一起使用,以指示浏览器加载非关键的第三方脚本,而不阻止文档解析器。初始网页加载或首次用户互动时不需要的脚本可能会被视为非关键型。

       <script src="https://example.com/script1.js" defer></script>
       <script src="https://example.com/script2.js" async></script>
    
  2. 使用预连接和 dns-prefetch 尽早建立与所需源的连接。这样关键脚本就可以提前开始下载。

       <head>
           <link rel="preconnect" href="http://PreconnThis.com">
           <link rel="dns-prefetch" href="http://PrefetchThis.com">
       </head>
    
  3. 在主页面内容加载完毕后或用户向下滚动到包含它们的网页部分时延迟加载第三方资源和嵌入内容。

Next.js 脚本组件

Next.js 脚本组件可实现上述脚本排序方法,并为开发者提供一个模板来定义其加载策略。一旦指定了合适的策略,该策略就会以最佳方式加载,而不会阻塞其他关键资源。

脚本组件在 HTML <script> 标记的基础上构建,并提供了一个选项,用于使用 strategy 属性来设置第三方脚本的加载优先级。

// Example for beforeInteractive:
<Script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserverEntry%2CIntersectionObserver" strategy="beforeInteractive" />

// Example for afterInteractive (default):
<Script src="https://example.com/samplescript.js" />

// Example for lazyonload:
<Script src="https://connect.facebook.net/en_US/sdk.js" strategy="lazyOnload" />

策略属性有三个值。

  1. beforeInteractive:此选项可用于在网页进入可交互状态之前执行的关键脚本。Next.js 可确保此类脚本被注入到服务器上的初始 HTML 中,并在其他自捆绑 JavaScript 之前执行。意见征求管理、漫游器检测脚本或呈现关键内容所需的帮助程序库都适合采用此策略。

  2. afterInteractive:这是应用的默认策略,效果等同于使用 defer 属性加载脚本。它应该用于浏览器在网页进入互动状态后可以运行的脚本,例如分析脚本。Next.js 会在客户端注入这些脚本,它们会在页面水化后运行。因此,除非另有说明,否则使用 Script 组件定义的所有第三方脚本都会由 Next.js 推迟,从而提供强有力的默认值。

  3. lazyOnload:此选项可用于在浏览器空闲时延迟加载低优先级脚本。此类脚本提供的功能在网页进入互动状态后无需立即使用,例如聊天或社交媒体插件。

开发者可以通过指定策略来告知 Next.js 其应用如何使用脚本。这样,框架便可以应用优化和最佳做法来加载脚本,同时确保最佳加载顺序。

利用 Script 组件,开发者可以将第三方脚本放置在应用中的任何位置(对于延迟加载的第三方)和文档级别(对于关键脚本)。也就是说,该脚本组件可以与使用该脚本的组件位于同一位置。饮水量后,脚本将注入到最初呈现的文档的头部或正文底部,具体取决于所使用的策略。

衡量影响

我们使用适用于 Next.js 商务应用入门博客的模板创建了两款演示版应用,以帮助衡量添加第三方脚本的影响。用于 Google 跟踪代码管理器和社交媒体嵌入的常用第三方最初会直接添加到这些应用的页面上,然后通过脚本组件加入到网页中。之后,我们在 WebPageTest 上比较了这些网页的性能。

Next.js 商务应用中的第三方脚本

已将第三方脚本添加到演示的商务应用模板中,如下所示。

之前 之后
Google 跟踪代码管理器(异步) 对于两个脚本,策略为 afterInteractive 的脚本组件
不带异步或延迟的 Twitter 关注按钮
演示 1 的脚本和脚本组件配置(包含 2 个脚本)。

以下比较显示了两个版本的 Next.js commerce starter-kit 的视觉进度。如上所示,使用正确的加载策略启用了脚本组件后,LCP 时间会提前将近 1 秒。

展示 LCP 改进的幻灯影片对比

Next.js 博客中的第三方脚本

向演示博客应用添加了第三方脚本,如下所示。

之前 之后
Google 跟踪代码管理器(异步) 对以下四个脚本执行策略为 lazyonload 的脚本组件
异步的 Twitter“关注”按钮
没有异步或推迟设置的 YouTube 订阅按钮
LinkedIn 关注按钮(不支持异步或推迟)
演示 2 的脚本和脚本组件配置,包含 4 个脚本。
展示使用和不使用 Script 组件时索引页面的加载进度的视频。使用脚本组件时,FCP 缩短了 0.5 秒。

如视频所示,首次内容绘制 (FCP) 发生在网页上没有脚本组件 0.9 秒时,使用脚本组件时为 0.4 秒。

脚本组件的后续步骤

虽然针对 afterInteractivelazyOnload 的策略选项可有效控制会阻塞渲染的脚本,但我们也在探索提高脚本组件实用性的其他选项。

使用网页工作器

Web Worker 可用于在后台线程上运行独立的脚本,从而释放主线程来处理界面任务并提高性能。Web Worker 最适合将 JavaScript 处理工作(而不是界面工作)分流到主线程以外。用于客户支持或营销的脚本通常不与界面交互,因此可能很适合在后台线程中执行。可以使用轻量级第三方库 PartyTown 将此类脚本隔离到 Web Worker 中。

鉴于 Next.js 脚本组件的当前实现,我们建议通过将策略设为 afterInteractivelazyOnload,将此类脚本延迟到主线程上。未来,我们提议引入一个新的策略选项 'worker',它可让 Next.js 使用 PartyTown 或自定义解决方案在 Web Worker 上运行脚本。我们欢迎开发者对此 RFC 发表评论。

最大限度降低 CLS

广告、视频或社交媒体信息流嵌入等第三方嵌入内容可能会导致在延迟加载时布局偏移。这会影响用户体验和网页的 Cumulative Layout Shift (CLS) 指标。通过指定要在其中加载嵌入内容的容器大小,可以最大限度地缩短 CLS。

“脚本”组件可用于加载可能导致布局偏移的嵌入内容。我们正在考虑进行增强,以提供有助于降低 CLS 的配置选项。这可以在脚本组件本身内提供,也可以作为配套组件提供。

封装容器组件

收录热门的第三方脚本(例如 Google Analytics [分析] 或 Google 跟踪代码管理器 [GTM])的语法和加载策略通常是固定的。对于每种脚本类型,这些内容可以被进一步封装在单独的封装容器组件中。我们只会向开发者提供一组最少的应用专用属性(例如跟踪 ID)。封装容器组件可以通过以下方式帮助开发者:

  1. 让他们能够更轻松地收录热门的脚本标签。
  2. 确保框架在后台使用最佳策略。

总结

第三方脚本通常是为了在使用方网站上添加特定功能而创建的。为了减少非关键脚本的影响,我们建议您推迟非关键脚本,Next.js 脚本组件会默认执行此操作。开发者可以保证,除非明确应用 beforeInteractive 策略,否则包含的脚本不会延迟关键功能。与 Next.js Script 组件一样,框架开发者也可以考虑在其他框架中构建这些功能。我们正在积极与 Nuxt.js 团队一起探索如何实现类似的组件。根据反馈,我们还希望进一步增强脚本组件,以涵盖更多用例。

确认

感谢 Kara EricksonJanicklas RalphKatie HempeniusPhilip WaltonJeremy WagnerAddy Osmani 对本博文的反馈。