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

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

Leena Sohoni
Leena Sohoni

在移动设备和桌面设备上投放的网站中,约有 45% 的请求是第三方请求,其中33% 是脚本。第三方脚本的大小、延迟时间和加载时间可能会显著影响网站的性能。Next.js Script 组件附带内置的最佳做法和默认值,可帮助开发者在其应用中引入第三方脚本,同时解决开箱即用的潜在性能问题。

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

借助第三方脚本,网站开发者可以利用现有解决方案来实现常用功能并缩短开发时间。但是,这些脚本的创建者通常没有任何动力考虑这会对使用方网站的性能造成的影响。对使用这些脚本的开发者来说,这些脚本也是黑盒。

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

应尽早加载布局所需的第三方库,以呈现网页。不必要在首次渲染时加载的第三方代码应延迟加载,以免阻塞主线程上的其他处理。Lighthouse 有两种审核方法,以标记阻塞渲染的脚本或主线程阻止脚本。

针对“移除阻塞渲染的资源”和“尽量减少第三方使用”的 Lighthouse 审核

请务必考虑网页的资源加载顺序,以免关键资源出现延迟,并且非关键资源不会阻塞关键资源。

虽然有可降低第三方影响的最佳实践,但并非所有人都知道如何针对所用的每个第三方实现这些实践。这可能很复杂,因为:

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

Aurora 专注于第三方脚本

Aurora 与开源 Web 框架和工具的协作之一是提供强大的默认设置和有主见的工具,帮助开发者改进用户体验的各个方面,例如性能、无障碍功能、安全性和移动设备准备情况。2021 年,我们专注于帮助框架堆栈改善用户体验和核心网页指标

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

为了解决主线程阻塞应用中使用的第三方脚本的问题,我们构建了脚本组件。该组件封装了序列化功能,可让开发者更好地控制第三方脚本加载。

在不使用框架组件的情况下对第三方脚本进行排序

可用的指南可帮助您减少呈现阻塞脚本的影响,其中提供了以下方法来高效加载和排序第三方脚本:

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

       <script src="https://example.com/script1.js" defer></script>
       <script src="https://example.com/script2.js" async></script>
    
  2. 使用预连接和 DNS 预提取尽早建立与所需源的连接。这样一来,关键脚本便能更早开始下载。

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

Next.js 脚本组件

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

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

// Example for beforeInteractive:
<Script src="https://cdnjs.cloudflare.com/polyfill/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" />

strategy 属性可以采用三种值。

  1. beforeInteractive:此选项可用于应在网页变为交互状态之前执行的关键脚本。Next.js 可确保将此类脚本注入到服务器上的初始 HTML 中,并在其他自捆绑 JavaScript 之前执行。意见征求管理、聊天机器人检测脚本或呈现重要内容所需的辅助库都非常适合这种策略。

  2. afterInteractive:这是应用的默认策略,相当于使用 defer 属性加载脚本。它应用于浏览器在网页可交互后可以运行的脚本,例如分析脚本。Next.js 会在客户端注入这些脚本,这些脚本会在页面呈现后运行。因此,除非另有说明,否则 Next.js 会推迟使用 Script 组件定义的所有第三方脚本,从而提供强大的默认设置。

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

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

使用 Script 组件,开发者可以在应用中的任意位置放置第三方脚本(用于延迟加载的第三方脚本),也可以在文档级别放置关键脚本。这意味着脚本组件可能与使用该脚本的组件位于同一位置。注入后,脚本将被注入到最初呈现的文档的标头或正文底部,具体取决于所使用的策略。

衡量影响

我们使用了 Next.js 商务应用入门博客的模板创建了两个演示版应用,以便衡量添加第三方脚本的影响。这些应用的网页上最初直接包含 Google 跟踪代码管理器和社交媒体嵌入的常用第三方,后来则通过脚本组件包含这些第三方。然后,我们使用 WebPageTest 比较了这些网页的效果。

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

我们在演示版的电子商务应用模板中添加了第三方脚本,如下所示。

之前 之后
使用异步功能的 Google 跟踪代码管理器 两个脚本的脚本组件,策略均为“afterInteractive”
不使用异步或延迟的 Twitter 关注按钮
包含 2 个脚本的演示版 1 的脚本和脚本组件配置。

以下对比展示了两个版本 Next.js 电子商务入门套件的视觉效果演变过程。如图所示,启用脚本组件并采用正确的加载策略后,LCP 提前了近 1 秒。

显示 LCP 改善情况的胶片条比较

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

我们在演示博客应用中添加了第三方脚本,如下所示。

之前 之后
使用异步功能的 Google 跟踪代码管理器 对 4 个脚本均使用策略 = lazyonload 的脚本组件
使用异步的 Twitter“关注”按钮
不使用异步或延迟的 YouTube 订阅按钮
不使用 async 或 defer 的 LinkedIn“关注”按钮
包含 4 个脚本的演示 2 的脚本和脚本组件配置。
视频演示了包含和不包含脚本组件的索引页的加载进度。使用脚本组件后,FCP 缩短了 0.5 秒。

如视频所示,在不使用脚本组件的情况下,网页上的 First Contentful Paint (FCP) 发生在 0.9 秒,而在使用脚本组件的情况下,发生在 0.4 秒。

脚本组件的后续发展

虽然 afterInteractivelazyOnload 的策略选项可对渲染阻塞脚本提供强大的控制功能,但我们还在探索其他可提高 Script 组件实用性的选项。

使用网页工作器

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 Script 组件默认会执行此操作。除非开发者明确应用 beforeInteractive 策略,否则可以放心,所包含的脚本不会延迟关键功能的执行。与 Next.js 脚本组件一样,框架开发者也可以考虑在其他框架中构建这些功能。我们正在积极与 Nuxt.js 团队探索推出类似组件。根据反馈,我们还希望进一步增强脚本组件,以涵盖更多用例。

致谢

感谢 Kara EricksonJanicklas RalphKatie HempeniusPhilip WaltonJeremy WagnerAddy Osmani 对这篇文章提供的反馈。