了解 Next.js 脚本组件背后的愿景,该组件提供了用于优化第三方脚本加载的内置解决方案。
在移动设备和桌面设备上运行的网站发出的请求中,约有 45% 是第三方请求,其中 33% 是脚本。第三方脚本的大小、延迟时间和加载情况都会显著影响网站的性能。Next.js Script 组件附带内置的最佳做法和默认值,可帮助开发者在其应用中引入第三方脚本,同时解决开箱即用的潜在性能问题。
第三方脚本及其对性能的影响
借助第三方脚本,网站开发者可以利用现有解决方案来实现常用功能并缩短开发时间。但是,这些脚本的创建者通常没有任何动力考虑这会对使用方网站的性能造成的影响。对于使用它们的开发者来说,这些脚本也是一个黑盒子。
脚本占用了大量第三方字节,以供各类网站跨不同类别的第三方请求下载。默认情况下,浏览器会根据脚本在文档中的位置确定其优先级,这可能会延迟对用户体验至关重要的脚本的发现或执行时间。
应尽早加载布局所需的第三方库以呈现网页。应延迟初始渲染所不需要的第三方,以免它们阻止主线程上的其他处理。Lighthouse 有两种审核方法,以标记阻塞渲染的脚本或主线程阻止脚本。
![Lighthouse 审查如何消除阻塞渲染的资源和尽量减少第三方使用量](https://developer.chrome.google.cn/static/blog/script-component/image/lighthouse-audits-elimin-fd1156fda5643.png?authuser=5&hl=hr)
请务必考虑网页的资源加载顺序,以确保关键资源不延迟,非关键资源不会阻碍关键资源。
虽然有一些最佳做法可以降低第三方的影响,但并不是所有人都知道如何在所使用的每个第三方都实施这些做法。这可能很复杂,因为:
- 在移动设备和桌面设备上,网站平均会使用 21 到 23 个不同的第三方(包括脚本)。具体使用情况和建议可能因具体情况而异。
- 实现许多第三方可能因使用的特定框架或界面库而异。
- 我们会经常引入较新的第三方库。
- 由于同一第三方的业务要求各不相同,因此开发者很难对该第三方的使用实现标准化。
Aurora 专注于第三方脚本
Aurora 与开源 Web 框架和工具的协作之一是提供强大的默认设置和专业工具,以帮助开发者改进用户体验的各个方面,例如性能、无障碍功能、安全性和移动就绪性。2021 年,我们专注于帮助框架堆栈改善用户体验及其核心网页指标指标。
为实现我们提高框架性能的目标,其中最重要的一步就是研究 Next.js 中第三方脚本的理想加载序列。Next.js 等框架具有独特的优势,能够提供实用的默认值和功能,帮助开发者高效地加载资源(包括第三方)。我们研究了广泛的 HTTP Archive 和 Lighthouse 数据,以找出哪些第三方在不同框架中阻止渲染最多。
为解决主线程阻止应用中使用的第三方脚本的问题,我们构建了脚本组件。该组件封装了排序功能,以便为开发者提供更好的第三方脚本加载控制机制。
在不使用框架组件的情况下对第三方脚本进行排序
为降低阻塞渲染的脚本的影响,我们提供的指南提供了以下方法来高效加载第三方脚本并对其进行排序:
使用带有
<script>
标记的async
或defer
属性,告知浏览器在不阻止文档解析器的情况下加载非关键的第三方脚本。初始网页加载或首次用户互动不需要的脚本可能会被视为非关键型。<script src="https://example.com/script1.js" defer></script> <script src="https://example.com/script2.js" async></script>
使用预连接和 DNS 预提取尽早建立与所需源的连接。这样关键脚本就能更早开始下载。
<head> <link rel="preconnect" href="http://PreconnThis.com"> <link rel="dns-prefetch" href="http://PrefetchThis.com"> </head>
延迟加载:在主页面内容加载完毕后或当用户向下滚动到包含它们的网页部分时,延迟加载第三方资源和嵌入内容。
Next.js 脚本组件
Next.js 脚本组件可实现上述脚本排序方法,并为开发者提供了一个模板,供其定义其加载策略。指定合适的策略后,该策略将以最佳方式加载,而不会阻塞其他关键资源。
脚本组件以 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" />
策略属性可以采用三个值。
beforeInteractive
:此选项可能用于应在网页变为可互动状态之前执行的关键脚本。Next.js 可确保将此类脚本注入到服务器上的初始 HTML 中,并在其他自捆绑 JavaScript 之前执行。呈现关键内容所需的意见征求管理、机器人检测脚本或帮助程序库都适合采用此策略。afterInteractive
:这是应用的默认策略,相当于加载具有 defer 属性的脚本。它应当用于浏览器可在页面进入互动后运行的脚本,例如分析脚本。Next.js 会在客户端注入这些脚本,并在网页水合后运行。因此,除非另有说明,否则使用 Script 组件定义的所有第三方脚本都会被 Next.js 延迟,从而提供强大的默认值。lazyOnload
:此选项可用于在浏览器处于空闲状态时延迟加载优先级较低的脚本。这类脚本提供的功能无需在网页进入可互动状态(例如聊天或社交媒体插件)后立即提供。
开发者可以通过指定策略来告知 Next.js 其应用如何使用脚本。这样一来,框架便可以应用优化和最佳实践来加载脚本,同时确保实现最佳加载顺序。
使用脚本组件,开发者可以将第三方脚本放置在应用中的任意位置,以便后期加载第三方;对于关键脚本,也可以将第三方脚本放置在文档级别。这意味着脚本组件可能与使用该脚本的组件位于同一位置。水合后,脚本将注入最初呈现的文档的头部或正文底部,具体取决于所使用的策略。
衡量影响
我们使用 Next.js 商务应用和入门博客的模板创建了两款演示应用,以帮助衡量添加第三方脚本的影响。用于 Google 跟踪代码管理器和社交媒体嵌入的常用第三方一开始就直接添加到这些应用的页面上,然后通过脚本组件进行添加。然后,我们使用 WebPageTest 比较了这些网页的效果。
Next.js 商务应用中的第三方脚本
向演示的商务应用模板中添加了第三方脚本,如下所示。
下面的对比显示了两个版本的 Next.js 商务入门套件的直观进度。可以看出,在启用脚本组件并采用正确的加载策略的情况下,LCP 发生的时间要早近 1 秒。
![展现 LCP 改进过程的幻灯影片对比图](https://developer.chrome.google.cn/static/blog/script-component/image/film-strip-comparison-sho-e0d0929f7666e.png?authuser=5&hl=hr)
Next.js 博客中的第三方脚本
向演示博客应用添加了第三方脚本,如下所示。
之前 | 之后 |
---|---|
将 Google 跟踪代码管理器与异步 | 对 4 个脚本均使用策略 = lazyonload 的脚本组件 |
使用异步跟踪方法的 Twitter 关注按钮 | |
不使用异步或延迟的 YouTube 订阅按钮 | |
不使用 async 或 defer 的 LinkedIn “关注”按钮 |
![展示带有和不带脚本组件的索引页面的加载进度的视频。使用脚本组件将 FCP 提升 0.5 秒。](https://developer.chrome.google.cn/static/blog/script-component/image/video-showing-loading-pr-6cc1f9330829c.gif?authuser=5&hl=hr)
如视频所示,在没有脚本组件的网页上,首次内容绘制 (FCP) 时间为 0.9 秒,在包含脚本组件的网页上为 0.4 秒。
脚本组件的后续步骤
虽然 afterInteractive
和 lazyOnload
的策略选项可让您有效控制阻塞渲染的脚本,但我们也在探索其他选项,以提高脚本组件的实用性。
使用网页工作器
Web Worker 可用于在后台线程上运行独立脚本,这样可以释放主线程来处理界面任务,并提高性能。Web Worker 最适合用于在主线程以外分流 JavaScript 处理,而不是界面工作。用于客户支持或营销的脚本通常不与界面交互,因此可能适合在后台线程上执行。您可以使用轻量级第三方库 PartyTown 将此类脚本隔离到 Web Worker 中。
根据 Next.js 脚本组件的当前实现,我们建议通过将策略设置为 afterInteractive
或 lazyOnload
来延迟这些脚本在主线程上执行。未来,我们提议引入 'worker'
这一新策略选项,让 Next.js 能够使用 PartyTown 或自定义解决方案在 Web Worker 上运行脚本。欢迎开发者对此 RFC 发表评论。
最大限度降低 CLS
第三方嵌入代码(例如广告、视频或社交媒体 Feed 嵌入代码)在延迟加载时可能会导致布局偏移。这会影响用户体验和网页的 Cumulative Layout Shift (CLS) 指标。您可以指定要加载嵌入内容的容器大小,从而将 CLS 降至最低。
脚本组件可用于加载可能导致布局偏移的嵌入代码。我们正在考虑对其进行增强,以提供有助于降低 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 对本博文提供反馈。