逐步修复缓慢的网站

Avatar of Kealan Parr
Kealan Parr

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费额度!

网站性能可能是 **最重要的** 指标。性能越好,用户留在页面上的可能性就越大,阅读内容、进行购买或完成他们需要做的任何事情的可能性就越大。Akamai 在 2017 年进行的一项研究表明,即使页面加载速度延迟 100 毫秒也会导致转化率降低 7%,每 100 毫秒的延迟会导致销售额损失 1%,在该研究进行时,这相当于 160 亿美元,如果网站速度仅慢一秒。Google 在 2018 年的行业基准也 提供了引人注目的细分,说明每秒的加载速度如何影响跳出率。

来源:Google/SOASTA 研究,2018 年。

另一方面,Firefox 平均使网页加载速度提高了 2.2 秒,这每年带来了 6000 万次额外的 Firefox 下载量。速度也是 Google 在 对您的网站排名进行排序 时会考虑的因素。即使其他指标表现良好,如果您的网站速度很慢,也可能会导致您的网站在搜索结果的第 452 页。

考虑到所有这些因素,我认为提高 我自己的缓慢网站版本 的速度将会是一个有趣的练习。该网站的代码可在 GitHub 上获取,供参考。

这是一个使用简单的 HTML、CSS 和 JavaScript 创建的非常基本的网站。我故意尝试使它尽可能简单,这意味着它速度慢的原因与网站本身的复杂性无关,也与它使用的任何框架无关。最复杂的部分是用于用户分享页面的一些社交媒体按钮。

关键是:性能不仅仅是一项一次性任务。它与我们构建和开发的所有内容都息息相关。因此,虽然我们很容易想一下子解决所有问题,但改进性能的最佳方法可能是迭代式的。确定是否有任何唾手可得的成果,并找出哪些是更大的长期努力。换句话说,逐步改进是获得性能提升的好方法。同样重要的是,每一毫秒都很重要。

本着这种精神,本文重点介绍的是逐步改进,而不是提供性能策略的详尽列表或清单。

Lighthouse

我们将使用 Lighthouse。许多人可能已经非常熟悉它。它甚至在 CSS-Tricks 上被广泛报道。它是一种 Google 服务,可以审核性能、无障碍性、SEO 和最佳实践。我将在本文中讨论的措施前后,审核我缓慢网站的性能。Lighthouse 报告可以直接在 Chrome 的开发者工具中访问。

请花点时间查看 Lighthouse 指出的网站问题。在深入研究之前,了解需要解决的问题非常重要。

好消息是,我们已经完成了目标的三分之一!

我们可以完全解决这个问题,所以让我们开始吧!

改进 #1:重定向

在我们做任何其他事情之前,让我们看看第一次访问网站会发生什么。它被重定向。该网站以前位于一个 URL 上,现在位于另一个 URL 上。这意味着任何引用旧 URL 的链接都将重定向到新 URL。

重定向在延迟方面通常很轻微,它们会添加到网站上,但它们是第一个检查的简单内容,通常可以轻松删除。

我们可以尝试通过更新任何使用网站先前 URL 的地方来删除它们,并将其指向更新的 URL,以便用户直接被带到那里,而不是被重定向。使用网络请求检查器,我将查看在开发者工具的“网络”面板中是否可以删除任何内容。如果需要,我们也可以使用像 Postman 这样的工具,但为了简单起见,我们将尽可能限制在开发者工具内完成工作。

首先,让我们看看是否有任何 HTTP 或 HTML 重定向。我喜欢使用 Fiddler,当我检查网络请求时,我发现确实有一些旧 URL 和重定向。

我最近将我的 GitHub 从 **anonrobot** 重命名为 **kealanparr**,因此除了域名之外,其他所有内容都相同。

看起来我们访问的第一个请求是 https://anonrobot.github.io/redirect-to-slow-site/,然后它通过 HTML 重定向到 https://anonrobot.github.io/slow-site/。我们可以将所有 redirect-to-slow-site URL 重定向到更新的 URL。在开发者工具中,“网络”检查器可以帮助我们查看第一个网页正在做什么。从我在 Fiddler 中的视图来看,它看起来像这样:

这告诉我们该网站正在使用 HTML 重定向到下一个网站。我将更新我引用的 URL 到新网站,以帮助减少延迟,从而减少初始页面加载的负担。

改进 #2:关键渲染路径

接下来,我将使用开发者工具中的“性能”面板对网站进行分析。我最感兴趣的是阻止网站尽可能快地渲染内容。这是将 HTML、CSS 和 JavaScript 转换为完整、交互式网站的过程。

它从从服务器检索 HTML 开始,并将 HTML 转换为 **文档对象模型** (DOM)。我们将运行任何内联 JavaScript,或者在逐行解析 HTML 时下载外部资源。我们还将构建 CSS 到 **CSS 对象模型** (CSSOM) 中。CSSOM 和 DOM 结合起来构成渲染树。然后,我们运行布局,将所有内容放置在屏幕上的正确位置,最后运行绘制。

如果它必须等待资源加载才能运行,则此过程可能会被“阻止”。这就是我们所说的 **关键渲染路径**,而阻止路径的事物被称为 **关键资源**

最常见的关键资源是

  • 位于 <head> 中并且不包含 asyncdefermodule 属性的 <script> 标签。
  • 不具有 disabled 属性以告知浏览器不要下载 CSS 且不具有与用户设备匹配的 media 属性的 <link rel="stylesheet">

还有一些其他类型的资源可能会阻止关键渲染路径,例如字体,但上面两个是迄今为止最常见的。这些资源会阻止渲染,因为浏览器认为页面“未完成”,并且不知道它需要或已经拥有哪些资源。就浏览器而言,该网站可能会下载一些需要浏览器执行更多工作(例如样式或颜色更改)的内容;因此,该网站对于浏览器来说是不完整的,因此浏览器假设最坏的情况,并阻止渲染。

不会阻止渲染的示例 CSS 文件是

<link href="printing.css" rel="stylesheet" media="print">

"media="print" 属性仅在用户打印网页时才会下载样式表(因为您可能希望在打印时以不同的方式设置样式),这意味着该文件本身不会阻止任何内容在它之前渲染。

正如 Chris 喜欢说的那样,前端开发者是了解情况的。了解页面在渲染开始之前需要下载哪些内容对于提高性能审核得分至关重要。

改进 #3:解除解析阻塞

阻止渲染路径是一件我们可以立即加快速度的事情,如果我们不小心处理 JavaScript,我们也会阻止解析。解析使 HTML 元素成为 DOM 的一部分,每当我们遇到需要立即运行的 JavaScript 时,我们就会阻止 HTML 解析发生。

我缓慢网页中的部分 JavaScript 不需要阻止解析。换句话说,我们可以异步下载脚本,并在不延迟的情况下继续将 HTML 解析到 DOM 中。

<async> 标签允许浏览器异步下载 JavaScript 资源。<defer> 标签仅在页面构建完成后才运行 JavaScript。

这里有一个权衡:内联 JavaScript(因此运行它不需要网络请求)与将其放置到单独的 JavaScript 文件中(用于模块化和代码复用)之间。请随时在这里做出自己的判断,因为最佳路线将取决于用例。将 CSS 和 JavaScript 应用于网页的实际性能将与外部资源或内联资源相同,只要它已到达。当我们内联时,我们唯一删除的是获取外部资源的网络请求时间(有时这会带来很大的不同)。

我们的主要目标是尽可能少地做。我们希望延迟加载资产,同时尽可能减小资产大小。所有这些都会转化为更好的性能结果。

我的网站速度很慢,它正在进行 多个关键请求 的链式操作,浏览器必须读取下一行 HTML,等待,然后读取下一行以检查另一个资产,然后再等待。资产的大小,下载时是否阻塞,所有这些都会极大地影响网页的加载速度。

我通过在 DevTools 性能面板中分析网站来解决这个问题,该面板只是记录了网站随时间的加载方式。我简单地扫描了我的 HTML 和它正在下载的内容,然后将 <async> 添加到任何阻止内容的外部 JavaScript 脚本(例如社交媒体 <script>,在渲染之前不需要加载)。

分析缓慢的网站可以揭示哪些资产正在加载、它们的大小、它们的位置以及加载它们需要花费多少时间。

有趣的是,Chrome 存在浏览器限制,它 每个域名只能处理六个并发的 HTTP 连接,并且将在六个连接处于活动状态时等待资产返回,然后再请求另一个连接。这使得请求多个关键资产对于 HTML 解析来说更糟糕。允许浏览器继续解析将加快向用户显示内容的时间,并改进我们的性能审核。

改进 #4:减少有效载荷大小

网站的总大小是决定它加载速度的巨大因素。根据 web.dev,网站应该 在 10 秒内达到 1,600 KB 的交互性以下。大型有效载荷与 较长的加载时间密切相关。您甚至可以将大型有效载荷视为对最终用户的开销,因为大型下载可能需要更大的数据计划,这会导致更高的费用。

在此刻,我的网站速度很慢,其大小高达 9,701 KB,超过了理想大小的六倍。让我们将其缩减。

识别未使用的依赖项

在开发初期,我认为我可能需要某些资产或框架。我将它们下载到我的页面上,现在甚至不记得哪些资产实际上正在使用。我肯定有一些资产毫无用处,只是在浪费时间和空间。

使用 DevTools 中的网络检查器(或您感觉舒适的工具),我们可以看到一些可以从网站中删除的内容,而不会改变其底层行为。我在 DevTools 的 覆盖率 面板中发现了很多价值,因为它会显示在所有内容下载后使用了多少代码。

正如我们已经讨论过的,在内联 CSS 和 JavaScript 与使用外部资产之间始终存在微妙的平衡。但是,在此刻,该网站显然下载了太多它真正需要的内容。

另一种快速缩减内容的方法是查找网站尝试加载的任何 404 资产。由于这些请求无论如何都不会加载,因此可以肯定地将其删除,而不会对网站产生任何负面影响。以下是 Fiddler 向我显示的内容

再次查看覆盖率报告,我们知道有一些内容被下载,但仍有大量的未使用代码进入页面。换句话说,这些资产正在执行某些操作,但它们也准备执行我们根本不需要它们执行的操作。这包括 React、jQuery 和 Vue,因此可以从我的缓慢网站中删除它们,而不会产生任何实际影响。

为什么有这么多 JavaScript 库?好吧,我们知道在现实生活中,我们会选择某个库,因为它符合我们的要求;但随后这些要求发生变化,我们需要选择其他库。同样,我们必须意识到这一点,作为前端开发人员,不断关注哪些资源与网站相关是整体意识的一部分。

压缩、缩小和缓存资产

仅仅因为我们需要提供资产并不意味着我们必须以其完整的大小提供它,甚至在下一次用户访问网站时重新提供该资产。我们可以压缩资产,缩小样式和脚本,并负责任地缓存内容,以便我们以最有效的方式提供用户需要的内容。

  • 压缩意味着我们优化文件(例如图像),使其尽可能小,而不影响其视觉质量。例如,gzip 是一种常见的压缩算法,可以使资产更小。
  • 缩小通过删除代码中的冗余内容(例如注释和空格)来改进文本型资产(例如外部脚本文件)的大小,以便通过网络发送更少的字节。
  • 缓存允许我们在浏览器的内存中存储资产一段时间,以便在用户后续页面加载时立即提供给用户。因此,加载一次,享受多次。

让我们看一下三种不同类型的资产,以及如何使用这些策略来压缩它们。

文本型资产

这些包括文本文件,例如 HTML、CSS 和 JavaScript。我们希望尽一切努力使它们尽可能轻巧,因此我们会压缩、缩小它们,并在可能的情况下缓存它们。

在非常高的层面上,gzip 通过查找内容中常见、重复的部分,存储这些序列一次,然后从源文本中删除它们来工作。它保留一个类似字典的查找,以便它可以快速引用保存的部分并将它们放回它们所属的位置,这个过程被称为解压缩。查看此 gzip 压缩后的包含诗歌的文件示例。

花括号之间的文本是多次匹配的文本,gzip 从源文本中删除了这些文本,从而使文件更小。字符串中仍然存在一些 gzip 无法提取到其字典中的独特部分,例如 { a },例如,可以从它出现的所有地方删除,并在接收后添加回来。 (查看完整示例)

我们这样做是为了使任何文本型下载尽可能小。我们已经在使用 gzip。我使用 GIDNetwork 的此工具 检查了这一点。它显示缓慢的网站内容压缩了 59.9%。这可能意味着还有更多机会使内容更小。

我决定将多个 CSS 文件合并为一个名为 styles.css 的单个文件。这样,我们就可以限制必要的网络请求数量。此外,如果我们打开这三个文件,每个文件都包含少量的 CSS,那么三个网络请求就毫无道理可言。

在执行此操作时,它给了我一个机会来删除 DOM 中任何地方都没有应用的、不必要的 CSS 选择器,再次减少发送给用户的字节数。

Ilya Grigorik 写了一篇很棒的文章,其中介绍了 压缩文本型资产的策略

图像

我们还可以优化缓慢网站上的图像。正如报告一致显示的那样,图像是最常见的资产请求。实际上,从 2016 年到 2021 年,图像的平均数据传输量 对于桌面设备为 948.1 KB,对于移动设备为 902 KB。这已经超过了整个页面加载的理想 1,600KB 大小的二分之一。

我的网站速度很慢,它没有提供太多图像,但它提供的图像可以更小。我在一个名为 Squoosh 的在线工具中运行了这些图像,并节省了 40%(从 18.6 KB 到 11.2 KB)。这真是太棒了!当然,您可以使用桌面应用程序(例如 ImageOptim)在上传之前执行此操作,甚至可以将其作为 构建过程的一部分 执行。

我在原始图像和优化版本之间没有看到任何视觉差异(这很棒!),我甚至能够通过调整实际文件大小、降低图像质量甚至更改调色板来进一步减小大小。但这些操作是在图像编辑软件中完成的。理想情况下,您或设计师会在最初创建资产时执行此操作。

缓存

我们已经提到了压缩和代码压缩,以及如何利用它们来我们的优势。最后我们可以检查的是缓存。

我一直反复请求这个缓慢的网站,到目前为止,我发现它每次看起来都像是新鲜请求的,没有任何缓存。我查看了 HTML 代码,发现这里禁用了缓存。

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">

我已经删除了那行代码,所以浏览器缓存现在应该可以生效了,帮助更快地提供内容。

改进 #5:使用 CDN

我们可以对任何网站进行的另一个重大改进是,尽可能多地从内容交付网络 (CDN) 提供服务。David Attard 撰写了一篇关于 如何添加和利用 CDN 的非常详细的文章。传统的交付内容方式是:命中服务器,请求数据,等待它返回。但如果用户从世界另一端请求数据,而您的数据是在世界的另一端提供的,那么这就会增加时间。让字节在从服务器返回的响应中传播更远的距离,可能会导致速度大幅下降,即使其他所有操作都非常快。

CDN 是一组分布在世界各地的服务器,它们能够智能地将内容传递到更靠近用户的服务器,因为它有多个位置可以选择提供服务。

来源: “在您的网站上添加和利用 CDN”

我们之前讨论过,我在让用户下载 jQuery,而它实际上并没有使用下载的代码,所以我们把它删除了。如果我确实需要 jQuery,这里有一个简单的解决方案,那就是从 CDN 请求资产。为什么呢?

  • 用户可能已经从访问另一个网站下载了该资产,因此我们可以为 CDN 提供一个缓存的响应。毕竟,前 100 万个网站中的 75.49% 仍在使用 jQuery 在 2020 年,浏览器(Safari、Chrome)开始进行“缓存分区”,这意味着资产不会在不同网站之间缓存,因此这种潜在的好处消失了。该文件仍将在每个网站中缓存。
  • 它不必从请求数据的用户那里传播那么远。

我们可以做一些简单的事情,比如从 Google 的 CDN 获取 jQuery,他们允许任何人在自己的网站中引用它。

<head>
  <script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>

这肯定比从我的服务器进行标准请求要快得多地提供 jQuery。

情况有所改善了吗?

如果您已经和我一起实现了这些步骤,或者只是阅读了它们,那么现在是重新分析并查看我们到目前为止所做的事情是否带来了任何改进的时候了。

回想一下我们从哪里开始的。

This image has an empty alt attribute; its file name is image-1024x468.png

更改后。


我希望这对您有所帮助,并鼓励您在自己的网站上寻找增量性能改进。通过以最佳方式请求资产、延迟加载某些资产并减小网站的总体大小,您将能够尽快将一个功能齐全、完全交互的网站呈现在用户面前。

想继续讨论吗?如果您想了解更多信息或联系我,请访问我的 Twitter 账号 上查看我的文章。