性能模式
优化加载第三方资源
简而言之:第三方资源会导致网站速度变慢,而且优化起来也很困难。您可以遵循某些最佳实践来高效地加载或延迟不同类型的第三方资源。您还可以使用框架级组件,例如 Next.js Script 组件,它提供了一个模板来构建加载第三方脚本的“何时”和“如何”。或者,像 Partytown 这样的实验性想法可能也会引起您的兴趣。
很难找到一个在孤立环境中运行的现代网站。大多数网站共存并依赖于网络上的其他多个来源来获取数据、功能、内容等等。任何位于其他域并被您的网站使用的资源对于您的网站来说都是第三方 (3P) 资源。网站上包含的典型第三方资源包括
- 地图、视频、社交媒体和聊天服务的嵌入
- 广告
- 分析组件和标签管理器
- A/B 测试和个性化脚本
- 提供可立即使用的辅助函数的实用程序库,例如用于数据可视化或动画的库。
- reCAPTCHA 或 CAPTCHA 用于检测机器人。
您可以使用第三方来集成其他功能,为您的内容增加价值,或减少从头开始构建网站时所涉及的一些繁琐工作。根据 2021 年 Web Almanac 报告,超过 94% 的网页 使用第三方资源 - 图像和 JavaScript 是对第三方内容贡献最大的因素。以下是一个有用的 细分,按内容类型和类别列出了第三方请求
虽然第三方资源可以通过有价值的功能丰富您的网站,但如果它们
- 会导致对每个必需资源进行额外的往返访问第三方域。
- 它们大量使用 JavaScript(影响下载和执行时间)或由于图像/视频未优化而体积庞大。
- 个别网站所有者无法影响它们的实现,而且它们的行为可能不可预测。
- 它们会阻止页面上其他关键资源的渲染,并影响 核心 Web 指标 (CWV)。
尽管存在这些问题,但第三方资源对于您的业务而言可能是必不可少的。如果您无法摆脱 3P 资源,那么最好的做法就是优化它们以减少性能影响 - 这正是我们将在本节中介绍的内容。
我们包含了一些适用于不同类型第三方脚本的策略和最佳实践。Next.js Script 组件整合了其中许多最佳实践,您可以在本文的第二部分了解有关它的信息。首先,让我们看看如何找出第三方脚本是否会影响页面的性能。
评估 3P 资源的性能影响
您可以使用多种方法来找出第三方代码如何影响您的网站。
-
以下 Lighthouse 审计可以帮助识别影响 CWV 的缓慢第三方脚本。
- 减少第三方代码的影响 用于阻止主线程的脚本。
- 减少 JavaScript 执行时间 用于执行时间过长的脚本
- 避免过大的网络负载 用于大型脚本
- 使用 WebPageTest (WPT) 的瀑布图来识别 阻止的第三方脚本,或使用 WPT 的并排比较来 衡量第三方标签的影响。
- 像 Bundlephobia 这样的网站可以帮助您评估将可用的 npm 包添加到捆绑包中的成本。您还可以使用 npm 包搜索 来查找任何包中包含的大小和依赖项。
了解了如何识别有问题的第三方代码,我们来探索优化它的方法。
优化策略
由于第三方代码不受您的控制,因此您无法直接优化这些库。这为您留下了两个选择。
-
替换或删除:如果第三方脚本提供的价值与其性能成本不成比例,请考虑将其删除。您还可以评估其他更轻量级但提供类似功能的替代方案。在这 个 案例研究中,我们讨论了如何通过使用更轻量级的替代方案和类似功能的包来提高电影应用程序的性能。
-
优化加载顺序:加载过程涉及在浏览器中加载多个第一方和第三方资源。为了设计最佳加载策略,您需要考虑浏览器为不同资源分配的优先级、它们在页面上的位置以及每个资源对网页的价值。我们提出了一种 适用于 React/Next.js 应用程序的最佳加载顺序。现在我们将看看这如何应用于各种第三方资源以及我们可以采取的最佳加载步骤。
高效加载 3P 脚本
以下是经过时间考验的最佳实践,如果使用得当,可以减少第三方资源的性能影响。
使用 async 或 defer 来防止脚本阻止其他内容。
适用对象: 非关键脚本(标签管理器、分析)
默认情况下,JavaScript 下载和执行是同步的,可能会阻塞主线程上的 HTML 解析器和 DOM 构建。在 async
或 defer
属性中使用 <script>
元素告诉浏览器异步下载脚本。您可以使用这些属性下载任何对关键渲染路径不必要的脚本(例如,主 UI 组件)。
defer
:在解析器执行的同时并行获取脚本,并将脚本执行延迟到解析器完成后。defer
应该是延迟执行到 DOM 构建完成后的默认选择。async
:在解析器执行的同时并行获取脚本,但只要脚本可用,即使会阻塞解析器,也会立即执行。对于具有依赖项的模块脚本,脚本及其所有依赖项都在defer
队列中执行。对于需要在加载过程中更早运行的脚本,使用async
。例如,您可能希望尽早执行特定的分析脚本,而不会错过任何早期的页面加载数据。
<script src="https://example.com/deferthis.js" defer></script>
<script src="https://example.com/asyncthis.js" async></script>
这里值得一提的一点是,async
和 defer
会降低浏览器为这些资源分配的优先级,导致它们加载得明显更晚。一个用于 优先级提示 的新功能可以帮助解决这个问题。
使用资源提示尽早建立与所需来源的连接
适用对象: 来自第三方 CDN 的关键脚本、字体、CSS、图像
由于 DNS 查找、重定向和每个第三方服务器可能需要的多次往返访问,连接到第三方来源可能会很慢。资源提示 dns-prefetch
和 preconnect
可以通过在生命周期早期启动连接来帮助减少此设置所需的时间。
包含对应于域的 dns-prefetch
资源提示会尽早执行 DNS 查找,从而减少与 DNS 查找相关的延迟。您可以将此与 preconnect
配合使用,以优化最关键的资源。preconnect
会通过执行 TCP 往返访问和处理 TLS 协商(除了 DNS 查找)来启动与第三方域的连接。
<head>
<link rel="preconnect" href="http://example.com" />
<link rel="dns-prefetch" href="http://example.com" />
</head>
我们关于 理想加载顺序 的文章提供了一个应该使用 preconnect
的第三方资源列表。
在 Andy Davies 讨论如何使用 preconnect
来缩短主产品图像的加载时间 的这个案例研究中,使用资源提示的优势显而易见,通过尽早启动与第三方图像 CDN 的连接。
“实际指标显示中位数提高了 400 毫秒,第 95 个百分位数提高了 1 秒以上。”
类似地,您可以使用资源提示来优化像机器人检测 (reCaptcha) 和同意管理这样的关键第三方的加载时间。
延迟加载页面下方 3P 资源
适用对象: 嵌入内容,例如 YouTube、地图、广告和社交媒体
像社交媒体信息流、广告、YouTube 视频和地图这样的第三方嵌入可能会减慢网页速度。但是,所有这些嵌入可能在页面加载时对用户不可见,并且可能会在用户向下滚动到它们时懒加载。您可以根据所需的浏览器支持使用不同的懒加载方法。
- 您可以将 loading 属性与用于加载 YouTube 或 Google 地图等第三方嵌入的图像和 iframe 配合使用。
- 使用 IntersectionObserver API 的自定义实现允许您检测观察到的元素何时进入或退出浏览器的视窗。
- Lazy-sizes - 一个流行的 JavaScript 库,为您实现懒加载。
懒加载嵌入的一种变体是在页面加载时向用户显示静态或动态外观。您可以使用地图嵌入的静态图像来显示地图嵌入上的特定区域,而不是地图嵌入。或者,您可以使用看起来像嵌入但只有在用户点击或与它交互时才加载的外观。一些用于实现流行嵌入的外观的方法包括用于地图的 地图静态 API、用于 Twitter 嵌入的 Tweetpik、用于 YouTube 的 lite-youtube-embed、用于聊天小部件的 React-live-chat-loader。关于这些技术的全面讨论可在此处获得 此处。
关于懒加载和外观的一些注意事项
- YouTube 外观的行为在 iOS 和 macOS 11+ 上的 Safari 上略有不同。第一次点击/点击会加载实际的视频嵌入。用户必须再次点击才能播放视频。
- 如果未指定嵌入的大小,懒加载会导致布局偏移并影响用户体验。为了防止布局偏移,您应该为所有懒加载的嵌入或它们的容器元素指定大小。
自托管 3P 脚本以防止往返
适用于:JavaScript 文件,字体
虽然预连接或 dns-prefetch 允许您尽早启动与第三方来源的连接,但仍然需要这些连接。此外,对于第三方来源,您需要依赖它们的缓存策略,这可能不是最佳的。
在同一来源自托管脚本副本为您提供了更多关于用于脚本的加载和缓存过程的控制。自托管减少了 DNS 查找所需的时间,并允许您使用 HTTP 缓存 改善脚本的缓存策略。您还可以使用 HTTP/2 服务器推送 来推送您知道用户会需要的脚本。一个 很好的例子 是如何自托管第三方脚本的 Casper.com,它通过自托管 Optimizely 提供的第三方脚本,将主页的开始渲染时间提高了 1.7 秒。
使用自托管的第三方脚本副本,您必须确保根据对原始脚本的更改定期更新您的副本。没有更新,脚本可能会过时,缺少重要的修复或与依赖项相对应的更改。在服务器而不是 CDN 上自托管也将阻止您利用 CDN 使用的 边缘缓存 机制。
尽可能使用服务工作者来缓存脚本
适用于:JavaScript 文件,字体
对于经常更改的脚本,自托管可能不是一种选择。您可以使用服务工作者来改进此类第三方脚本的缓存,同时还可以利用 CDN 边缘缓存。此技术使您可以更好地控制网络上重新获取的频率。此技术可以与预连接结合使用,以进一步降低获取操作的网络成本。您还可以加载资源,以便对非必需第三方脚本的请求推迟到页面到达关键用户时刻为止。
遵循理想的加载顺序
考虑以上针对不同类型的第三方及其对页面的价值的指导。根据每种资源的预期用途,您可以按照 理想的资源加载顺序 来最佳地交织第一方和第三方资源,以实现更快的页面加载。
按脚本类型划分的最佳实践
有些脚本比其他脚本更容易优化。与网络性能专家讨论优化不同的第三方,观察到的典型约束以及他们对加载第三方的愿望清单,让我们得出了一些有趣的结论。普遍共识是,大多数用户在一定阈值的内容可见之前不会与站点交互。以下是针对不同脚本类型的具体指导。
非关键 JavaScript
大多数第三方(如聊天小部件或分析脚本)对于用户体验来说并不重要,可以延迟。使用 defer
脚本属性是延迟加载和执行这些脚本的最常见方法。
广告或分析团队可能会担心延迟脚本对应用程序的可见性和广告收入的影响。 电报案例研究 通常在这种情况中被引用,其中延迟所有脚本不会歪曲任何分析或广告指标。相反,第一个广告加载指标平均提高了 4 秒。一些开发人员还设计了 延迟第三方加载直到页面变为可交互 的解决方案。
机器人检测/ReCaptcha
由于您希望阻止机器人访问网页表单,因此开发人员通常会尽早加载这些脚本。但是,ReCaptcha 具有相当大的 JS 负载和主线程占用空间,因此有动机延迟加载它,直到需要为止。优化此脚本的一些方法是
- 仅在几个页面上加载它,这些页面包含来自用户的可能会被机器人垃圾邮件的表单输入。
- 懒加载脚本,例如,在用户与表单元素交互时,在表单获得焦点时。
- 使用资源提示在您需要脚本在页面加载时执行时建立早期连接。
Google 标签管理器 (GTM)
大型站点通常会向营销团队或代理商提供 Google 标签管理器 访问权限。这使他们能够将新的营销标签添加到站点的所有页面,以实现更好的跟踪。性能不是营销团队的主要关注点,并且他们可能并不知道不加考虑地添加标签可能会减慢网站速度。GTM 脚本的优化更多地是关于 控制谁可以访问 GTM 以及监控他们所做的更改。
您可以从确保网站所有者拥有帐户而不是外部代理商开始。这使您可以为谁可以添加、编辑和发布标签定义细粒度的访问权限。 更好地协作 开发和营销部门可以建立起来以审计新的标签并删除未使用的标签。
您的网站可能不需要在所有页面上都使用 GTM。(例如,没有理由让营销团队跟踪电子商务网站结账页面的事件)。应分别审计页面,以便可以删除不必要的 GTM 包含项。使用 cookie 横幅的网站也可以选择在用户拒绝 cookie 时不加载 GTM。最后,如果您必须在页面上加载 GTM,则可以延迟脚本以在加载主要内容后触发。
另一种适用于较旧的第三方脚本标签的优化与 document.write() 相关。使用 document.write() 注入脚本是不安全的,并且可能会导致基于浏览器和脚本类型的警告或错误。一些第三方脚本仍然使用这种方法。GTM 在其 自定义 HTML 标签创建界面 中提供了名为支持 document.write() 的配置。如果启用此选项,Google 标签管理器会暂时将默认的 document.write() 函数替换为它自己的安全版本。
A/B 测试和个性化
网站进行 A/B 测试 以检查哪个版本的网页效果更好。在已识别的用户样本中,为不同的用户加载页面中的两个变体之一。A/B 测试会显着影响运行它们的页面的性能,每次测试都会增加 1 秒的加载时间。目前,许多 A/B 测试是通过第三方外部获取的,开发人员对执行以更改这些测试的 UI 的 JavaScript 代码几乎没有控制权。
网站个性化 是一个相关的概念,它涉及运行脚本以根据已知数据为不同的用户提供定制的体验。这些脚本同样很繁重,难以优化。与 A/B 测试脚本一样,个性化脚本也需要尽早运行,因为渲染的 UI 依赖于脚本的输出。为 A/B 测试开发 基于服务器的自定义解决方案 和个性化是优化 A/B 测试的理想方法。但是,它可能并不总是可行。
为了优化第三方 A/B 测试脚本,您可以限制接收脚本的用户数量。该脚本根据启发式方法识别要显示哪个版本,并为用户启用正确的版本。这可能会减慢所有用户的页面速度。Google Optimize 允许配置 针对用户的规则。许多这些规则可以在 Google 服务器上进行评估,因此 对未被定位的用户的影响很小。
YouTube 和地图嵌入
这些嵌入很重,开发人员必须探索懒加载或点击加载模式来加载嵌入以优化它们。鼓励使用 lite-youtube-embed 等解决方案,同时注意在 iOS/macOS-Safari 上使用此外观播放视频需要双击/点击。
社交媒体嵌入
一些社交媒体嵌入提供了延迟加载脚本的选项(例如,Facebook 嵌入中的 data-lazy)。你可以探索这个选项来提高性能。另一个选择是使用手动创建或使用工具(如 tweetpik)创建的图像占位符。
开箱即用的优化
为了优化第三方脚本,开发团队应该了解资源提示、延迟加载、HTTP 缓存和服务工作者的细微差别,并在他们的解决方案中实现这些功能。一些框架和库已经封装了这些最佳实践,使开发者能够轻松使用。
由 Builder.io 创建的 Partytown 是一个实验性库,可以帮助在 Web Worker 上运行资源密集型脚本,而不是在主线程上。他们的理念是,主线程应该专用于你的代码,任何非关键路径所需的脚本都可以被隔离到 Web Worker 中。Partytown 允许你配置对主线程 API(如 cookie、localStorage、userAgent 等)的访问。API 调用也可以记录参数,以便更好地了解脚本的功能。
JavaScript 代理和服务工作者处理 Web Worker 和主线程之间的通信。Partytown 脚本必须与 HTML 文档托管在同一服务器上。它可以与 React 或 Next.js 应用程序一起使用,甚至无需任何框架。每个可以在 Web 服务器上执行的第三方脚本都应该将它的开头 script 标签的 type 属性设置为 text/partytown,如下所示。
<script type="text/partytown">// Third-party analytics scripts</script>
该库还提供了一个 React Partytown 组件,你可以直接将其包含在你的 React 或 Next.js 项目中。它可以包含在文档的 <head>
中,如下所示,适用于 Next.js 文档。
import { Partytown } from '@builder.io/partytown/react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<Partytown />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
Partytown 还包含用于常见分析库(如 Google Tag Manager)的 React 组件。以下示例展示了如何在 React/Next.js 项目中添加它。
import { Partytown, GoogleTagManager, GoogleTagManagerNoScript } from '@builder.io/partytown/react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<GoogleTagManager containerId={'GTM-XXXXX'} />
<Partytown />
</Head>
<body>
<GoogleTagManagerNoScript containerId={'GTM-XXXXX'} />
<Main />
<NextScript />
</body>
</Html>
);
}
Next.js 本身通过其 Script 组件提供了对第三方脚本的开箱即用优化。让我们看看这如何帮助我们提高不同第三方脚本的加载性能。
Next.js Script
组件
Next.js 11 于 2021 年中期发布,其组件基于 Google Aurora 团队引入的 Conformance 方法。Conformance 是一个提供精心设计的解决方案和规则来支持最佳加载和核心 Web 指标的系统。Conformance 将最佳实践编码到规则集中,开发者可以轻松地实施这些规则。强大的默认值和可操作的规则构成了该系统的基础。它们使开发者能够轻松地做正确的事情,并防止反模式的出现。
Next.js Script 组件 通过提供可自定义的模板来提高加载性能,从而使用 Conformance。Script 组件封装了 <script>
标签,并允许你使用 strategy 属性设置第三方脚本的加载优先级。strategy 属性可以取三个值。
- beforeInteractive:对于浏览器应该在页面变为交互式之前执行的关键脚本使用此属性。(例如,机器人检测)
- afterInteractive:对于浏览器可以在页面变为交互式后运行的脚本使用此属性。(例如,标签管理器)这是应用的默认策略,等同于使用
defer
加载脚本 - lazyOnload:对于可以在浏览器空闲时延迟加载的脚本使用此属性。
设置 strategy 属性有助于 Next.js 自动应用优化和最佳实践来加载脚本,同时确保最佳加载顺序。你可以使用带有 strategy 属性的 script 标签,如下所示。与原生 HTML script 标签不同,你不应该将 next/script 标签放在 next/head
组件或 pages/document.js
中。
之前
import Head from "next/head";
export default function Home() {
return (
<>
<Head>
<script async src="https://example.com/samplescript.js" />
</Head>
</>
);
}
之后
// pages/index.js
// default strategy afterinteractive will apply when strategy not specified.
import Script from 'next/script'
<br>
export default function Home() {
return (
<>
<Script src="https://example.com/samplescript.js" />
</>
)
}
Script 组件允许你解决前面讨论的许多用例。你可以使用它来加载用于分析、社交媒体、实用程序库和其他用途的第三方脚本。以下示例演示了如何将上述策略应用于不同类型的第三方脚本。
尽早加载 polyfills
在你想尽早加载应用于核心内容的特定 polyfills 的情况下,你可以使用 beforeInteractive 策略加载 polyfill,如下面的 Next.js 文档 中的示例所示。
import Script from "next/script";
export default function Home() {
return (
<>
<Script
src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserverEntry%2CIntersectionObserver"
strategy="beforeInteractive"
/>
</>
);
}
延迟加载社交媒体嵌入
社交媒体嵌入,特别是那些在页面加载时不可见的嵌入,可以延迟或延迟加载,例如当用户滚动到它们时或在空闲时间。你可以使用 lazyonload
策略,如下面的 代码片段 所示。
import Script from "next/script";
export default function Home() {
return (
<>
<Script
src="https://connect.facebook.net/en_US/sdk.js"
strategy="lazyOnload"
/>
</>
);
}
在加载时有条件地执行代码
可能有一些代码需要在特定第三方脚本加载后执行。这可以在 script 组件的 onload 属性中指定。例如,下面的 代码片段 展示了如何包含基于用户同意执行的代码。
<Script
src={url} // consent management
strategy="beforeInteractive"
onLoad={() => {
// If loaded successfully, then you can load other scripts in sequence
}}
/>
在 script 标签中使用内联脚本
需要根据第三方组件加载执行的内联脚本也可以包含在 Script 组件中,如 此处 所示。
import Script from 'next/script'
<Script id="show-banner" strategy="lazyOnload">
{`document.getElementById('banner').removeClass('hidden')`}
</Script>
// or
<Script
id="show-banner"
dangerouslySetInnerHTML={{
__html: `document.getElementById('banner').removeClass('hidden')`
}}
/>
此处,内联脚本用于在延迟加载第三方横幅广告后更改其可见性。请注意,内联脚本也可以使用 dangerouslySetInnerHTML 属性包含。
将属性转发到第三方脚本
你可以在 Script 组件中设置特定的属性值,这些值可以被第三方脚本使用。下面的 示例 展示了如何将两个这样的属性传递给分析脚本。
import Script from "next/script";
export default function Home() {
return (
<>
<Script
src="https://www.google-analytics.com/analytics.js"
id="analytics"
nonce="XUENAJFW"
data-test="analytics"
/>
</>
);
}
加载分析脚本
在你的网站上包含分析信息的方法有很多,可以使用 Google Analytics(GA) 和 Google Tag Manager(GTM)。你可以使用 Script 组件在你的 Next.js 网站上以最佳方式加载 gtag.js 或 analytics.js 脚本。根据你希望执行这些脚本的位置,你可以在 _app.js(适用于所有页面)或特定页面上加载它们。
GTM 可以通过将 script 组件包含在 _app.js 中来为网站上的所有页面启用,如下所示
import Script from "next/script";
// + other imports
function MyApp({ Component, pageProps }) {
// Other app code
return (
<>
{/* Google Tag Manager - Global base code */}
<Script
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://127.0.0.1/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer', '${GTM_ID}');
`,
}}
/>
<Component {...pageProps} />
</>
);
}
export default MyApp;
相反,如果你想在特定页面上加载 analytics.js,你可以在该页面上包含它,如下所示。
import Script from "next/script";
//other imports
const Home = () => {
return (
<div class="container">
<Script id="google-analytics" strategy="afterInteractive">
{`
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
`}
</Script>
</div>
//Other UI related HTML
);
};
import Script from "next/script";
//other imports
const Home = () => {
return (
<div class="container">
<Script id="google-analytics" strategy="afterInteractive">
{`
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
`}
</Script>
</div>
//Other UI related HTML
);
};
export default Home;
请注意,在上面两个示例中,分析脚本都是使用 strategy = afterInteractive 加载的。
结论
在组合你的网页时,将来自你服务器的资源与来自网络其他角落的资源相结合,你必须经常监测这些资源之间的相互作用。你可以从正确地排序资源并遵循最佳实践开始。你也可以依靠将这些最佳实践内置到其设计中的框架或解决方案。
随着网站的增长,性能报告和定期审计可以帮助消除冗余并优化影响性能的脚本。最后,我们始终希望那些存在普遍已知性能问题的第三方能够优化其代码或公开 API,以支持解决这些问题。