了解 Next.js 的 Script 组件背后的愿景,该组件提供了一种内置解决方案来优化第三方脚本的加载。
在移动设备和桌面设备上投放的网站发出的请求中,大约 45% 是第三方请求,其中 33% 是脚本。第三方脚本的大小、延迟时间和加载方式可能会对网站性能产生重大影响。Next.js 脚本组件内置了最佳实践和默认设置,可帮助开发者在应用中引入第三方脚本,同时开箱即用地解决潜在的性能问题。
第三方脚本及其对性能的影响
第三方脚本可让 Web 开发者利用现有解决方案来实现常见功能并缩短开发时间。但这些脚本的创建者通常没有动力去考虑对使用这些脚本的网站的性能影响。对于使用这些脚本的开发者来说,这些脚本也是一个黑盒。
在不同类别的第三方请求中,脚本占网站下载的第三方字节的很大一部分。默认情况下,浏览器会根据脚本在文档中的位置来确定其优先级,这可能会延迟发现或执行对用户体验至关重要的脚本。
应尽早加载布局所需的第三方库,以便呈现网页。初始渲染不需要的第三方代码应延迟加载,以免阻塞主线程上的其他处理。Lighthouse 有两项审核,用于标记会阻塞渲染或主线程的脚本。
请务必考虑网页的资源加载顺序,以免关键资源延迟加载,以及非关键资源阻塞关键资源。
虽然有一些最佳实践可以减少第三方的影响,但并非所有人都知道如何针对他们使用的每个第三方实施这些最佳实践。这可能会很复杂,原因如下:
- 平均而言,网站在移动端和桌面端会使用 21 到 23 个不同的第三方(包括脚本)。每种类型的用法和建议可能有所不同。
- 实现许多第三方的方式可能会因是否使用特定框架或界面库而异。
- 新的第三方库会频繁推出。
- 与同一第三方相关的业务要求各不相同,这使得开发者很难标准化其使用。
Aurora 对第三方脚本的关注
Aurora 与开源 Web 框架和工具合作的部分目的是提供强大的默认设置和固执己见的工具,帮助开发者改进用户体验的各个方面,例如性能、无障碍功能、安全性和移动就绪性。2021 年,我们专注于帮助框架堆栈提升用户体验并改善其 Core Web Vitals 指标。
为了实现提升框架性能的目标,我们采取了多项重要举措,其中之一就是研究 Next.js 中第三方脚本的理想加载顺序。Next.js 等框架具有独特的优势,可提供实用的默认设置和功能,帮助开发者高效加载资源(包括第三方资源)。我们研究了大量 HTTP Archive 和 Lighthouse 数据,以找出哪些第三方在不同框架中阻止渲染的情况最多。
为了解决应用中使用的第三方脚本阻塞主线程的问题,我们构建了脚本组件。该组件封装了排序功能,可为开发者提供更好的第三方脚本加载控制。
在没有框架组件的情况下对第三方脚本进行排序
现有指南中提供了以下方法,可减少渲染阻塞脚本的影响,从而高效加载和排序第三方脚本:
将
async或defer属性与<script>标记搭配使用,指示浏览器在不阻塞文档解析器的情况下加载非关键第三方脚本。初始网页加载或带来用户首次互动的脚本可能被视为非关键脚本。<script src="https://example.com/script1.js" defer></script> <script src="https://example.com/script2.js" async></script>使用 preconnect 和 dns-prefetch 尽早建立与所需源的连接。这样,关键脚本就可以更早开始下载。
<head> <link rel="preconnect" href="http://PreconnThis.com"> <link rel="dns-prefetch" href="http://PrefetchThis.com"> </head>在主页面内容加载完毕后,或在用户向下滚动到包含第三方资源和嵌入内容的页面部分时,延迟加载这些资源和内容。
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 属性可以采用三个值。
beforeInteractive:此选项可用于应在网页进入互动状态之前执行的关键脚本。Next.js 可确保将此类脚本注入服务器上的初始 HTML 中,并在其他自行捆绑的 JavaScript 之前执行。意见征求管理、漫游器检测脚本或呈现关键内容所需的辅助库都非常适合采用此策略。afterInteractive:这是应用的默认策略,相当于加载具有 defer 属性的脚本。它应该用于浏览器可在网页可互动后运行的脚本,例如分析脚本。Next.js 会在客户端注入这些脚本,这些脚本会在网页水合后运行。因此,除非另有指定,否则使用 Script 组件定义的所有第三方脚本都会被 Next.js 推迟,从而提供强大的默认设置。lazyOnload:此选项可用于在浏览器空闲时延迟加载低优先级脚本。页面变为可互动状态后,不需要立即提供此类脚本提供的功能,例如聊天或社交媒体插件。
开发者可以通过指定策略来告知 Next.js 其应用如何使用脚本。这样,框架就可以应用优化和最佳实践来加载脚本,同时确保最佳加载顺序。
借助 Script 组件,开发者可以将第三方脚本放置在应用中的任何位置,以便延迟加载第三方脚本,也可以将关键脚本放置在文档级。这意味着 Script 组件可能与使用该脚本的组件位于同一位置。水合后,脚本将被注入到初始渲染文档的头部或正文底部,具体取决于所使用的策略。
衡量影响
我们使用 Next.js 电商应用和入门博客的模板创建了两个演示应用,以帮助衡量添加第三方脚本的影响。这些应用的页面上最初直接包含 Google 跟踪代码管理器和社交媒体嵌入常用的第三方,后来通过 Script 组件包含。然后,我们在 WebPageTest 上比较了这些网页的性能。
Next.js 商务应用中的第三方脚本
第三方脚本已添加到商务应用模板中,用于演示,如下所示。
以下对比展示了两个版本的 Next.js 商务入门套件的视觉进度。如您所见,在启用 Script 组件并采用正确的加载策略后,LCP 提前了近 1 秒。
Next.js 博客中的第三方脚本
第三方脚本已添加到演示博客应用中,如下所示。
| 之前 | 之后 |
|---|---|
| Google 跟踪代码管理器(异步) | 四个脚本中每个脚本的 strategy = lazyonload 的脚本组件 |
| Twitter 关注按钮(异步) | |
| 不带 async 或 defer 的 YouTube“订阅”按钮 | |
| 不含 async 或 defer 的 LinkedIn“关注”按钮 |
如视频所示,在不使用 Script 组件的情况下,First Contentful Paint (FCP) 发生在 0.9 秒,而在使用 Script 组件的情况下,FCP 发生在 0.4 秒。
脚本组件的未来发展
虽然 afterInteractive 和 lazyOnload 的策略选项可对渲染阻塞脚本提供显著控制,但我们也在探索其他选项,以提高脚本组件的实用性。
使用网页工作器
Web worker 可用于在后台线程上运行独立脚本,从而释放主线程来处理界面任务并提升性能。Web Worker 最适合将 JavaScript 处理(而非界面工作)从主线程中分流。用于客户服务或营销的脚本通常不与界面交互,因此可能适合在后台线程上执行。可以使用轻量级第三方库 PartyTown 将此类脚本隔离到 Web Worker 中。
对于当前实现的 Next.js 脚本组件,我们建议通过将策略设置为 afterInteractive 或 lazyOnload,在主线程上延迟这些脚本。未来,我们计划引入一个新的策略选项 'worker',让 Next.js 能够使用 PartyTown 或自定义解决方案在 Web Worker 上运行脚本。我们欢迎开发者就此 RFC 提供反馈。
最大限度地减少 CLS
广告、视频或社交媒体信息流嵌入等第三方嵌入内容在延迟加载时可能会导致布局偏移。这会影响用户体验和网页的累积布局偏移 (CLS) 指标。通过指定嵌入内容加载位置的容器大小,可以最大限度地减少 CLS。
Script 组件可用于加载可能导致布局偏移的嵌入内容。我们正在考虑对其进行扩充,以提供有助于降低 CLS 的配置选项。这可以在脚本组件本身中提供,也可以作为配套组件提供。
封装容器组件
用于添加热门第三方脚本(例如 Google Analytics 或 Google 跟踪代码管理器 [GTM])的语法和加载策略通常是固定的。这些脚本可以进一步封装在针对每种脚本类型的单独封装容器组件中。开发者只能访问一组最少的应用专属属性(例如跟踪 ID)。封装容器组件将通过以下方式帮助开发者:
- 让用户可以更轻松地添加热门脚本标记。
- 确保框架在后台使用最理想的策略。
总结
创建第三方脚本通常是为了在消费网站中添加特定功能。为了减少非关键脚本的影响,我们建议您推迟加载这些脚本,Next.js 脚本组件默认会这样做。开发者可以确信,除非他们明确应用 beforeInteractive 策略,否则包含的脚本不会延迟关键功能。与 Next.js Script 组件一样,框架开发者也可以考虑在其他框架中构建这些功能。我们正在积极探索与 Nuxt.js 团队合作推出类似组件。根据反馈,我们还希望进一步增强脚本组件,以涵盖更多应用场景。
致谢
感谢 Kara Erickson、Janicklas Ralph、Katie Hempenius、Philip Walton、Jeremy Wagner 和 Addy Osmani 对本文提供的反馈。