了解 Next.js 的 Script 组件背后的愿景。该组件提供了一种内置解决方案,可优化第三方脚本的加载。
在移动设备和桌面设备上投放的网站中,约有 45% 的请求是第三方请求,其中33% 是脚本。第三方脚本的大小、延迟时间和加载时间可能会显著影响网站的性能。Next.js Script 组件内置了最佳实践和默认设置,可帮助开发者在应用中引入第三方脚本,同时开箱即用地解决潜在的性能问题。
第三方脚本及其对性能的影响
借助第三方脚本,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 会在客户端注入这些脚本,这些脚本会在页面呈现后运行。因此,除非另有说明,否则 Next.js 会推迟使用 Script 组件定义的所有第三方脚本,从而提供强大的默认设置。lazyOnload
:此选项可用于在浏览器空闲时延迟加载低优先级脚本。在网页变为交互式后,不需要立即使用此类脚本提供的功能,例如聊天或社交媒体插件。
开发者可以通过指定策略告知 Next.js 其应用如何使用脚本。这样,框架便可应用优化和最佳实践来加载脚本,同时确保最佳加载顺序。
使用 Script 组件,开发者可以在应用中的任意位置放置第三方脚本(用于延迟加载的第三方脚本),也可以在文档级别放置关键脚本。这意味着,脚本组件可以与使用该脚本的组件共存。注入后,脚本将被注入到最初呈现的文档的标头或正文底部,具体取决于所使用的策略。
衡量影响
我们使用了 Next.js 商务应用和入门博客的模板创建了两个演示版应用,以帮助衡量添加第三方脚本的影响。这些应用的网页上最初直接包含 Google 跟踪代码管理器和社交媒体嵌入的常用第三方,后来则通过脚本组件包含这些第三方。然后,我们在 WebPageTest 上比较了这些网页的性能。
Next.js 商务应用中的第三方脚本
我们在演示版的电子商务应用模板中添加了第三方脚本,如下所示。
以下对比图展示了两个版本 Next.js 电子商务入门套件的视觉效果演变过程。如图所示,启用脚本组件并采用正确的加载策略后,LCP 提前了近 1 秒。
Next.js 博客中的第三方脚本
我们在演示博客应用中添加了第三方脚本,如下所示。
之前 | 之后 |
---|---|
使用异步功能的 Google 跟踪代码管理器 | 针对这四个脚本中的每一个脚本,脚本组件策略为 lazyonload |
使用异步的 Twitter“关注”按钮 | |
不使用异步或延迟的 YouTube 订阅按钮 | |
不使用 async 或 defer 的 LinkedIn“关注”按钮 |
如视频所示,在不使用脚本组件的情况下,网页上的 First Contentful Paint (FCP) 发生时间为 0.9 秒,而在使用脚本组件的情况下,发生时间为 0.4 秒。
脚本组件的后续发展
虽然 afterInteractive
和 lazyOnload
的策略选项可对渲染阻塞脚本提供强大的控制功能,但我们还在探索其他可提高 Script 组件实用性的选项。
使用网页工作器
Web Worker 可用于在后台线程上运行独立脚本,从而释放主线程来处理界面任务并提升性能。Web Worker 最适合将 JavaScript 处理(而非界面工作)从主线程分流。用于客户服务或营销的脚本通常不会与界面互动,因此非常适合在后台线程中执行。您可以使用轻量级第三方库 PartyTown 将此类脚本隔离到 Web Worker 中。
对于 Next.js 脚本组件的当前实现,我们建议将策略设置为 afterInteractive
或 lazyOnload
,以便在主线程中推迟这些脚本。未来,我们提议引入新的策略选项 'worker'
,以便 Next.js 使用 PartyTown 或自定义解决方案在 Web Worker 上运行脚本。欢迎开发者就此 RFC 提供反馈。
最小化 CLS
广告、视频或社交媒体信息流嵌入等第三方嵌入内容在延迟加载时可能会导致布局偏移。这会影响网页的用户体验和 Cumulative Layout Shift (CLS) 指标。您可以通过指定要加载嵌入内容的容器的大小来最大限度地减少 CLS。
脚本组件可能会用于加载可能会导致布局偏移的嵌入内容。我们正在考虑对其进行扩展,以提供有助于降低 CLS 的配置选项。这可以通过脚本组件本身提供,也可以作为配套组件提供。
封装容器组件
包含 Google Analytics 或 Google 跟踪代码管理器 (GTM) 等热门第三方脚本的语法和加载策略通常是固定的。这些方法可以进一步封装在适用于每种脚本类型的各个封装容器组件中。开发者只能使用一小部分特定于应用的属性(例如跟踪 ID)。封装容器组件可帮助开发者:
- 让他们更轻松地添加常用脚本代码。
- 确保框架在后台使用最优策略。
总结
第三方脚本通常用于在使用方网站中添加特定功能。为减少非关键脚本的影响,我们建议您延迟执行这些脚本,Next.js Script 组件默认会执行此操作。除非开发者明确应用 beforeInteractive
策略,否则可以放心,所包含的脚本不会延迟关键功能的执行。与 Next.js 脚本组件一样,框架开发者也可以考虑在其他框架中构建这些功能。我们正在积极与 Nuxt.js 团队探索推出类似组件。根据反馈,我们还希望进一步增强脚本组件,以涵盖更多用例。
致谢
感谢 Kara Erickson、Janicklas Ralph、Katie Hempenius、Philip Walton、Jeremy Wagner 和 Addy Osmani 对这篇文章提供的反馈。