页面速度是一个复杂的话题,为了提高各方面的速度,许多现有的文章都列好了需要采取的措施以及需要安装的插件列表,这很好但要注意每个网站都不同。所以在这篇文章中我将帮助你了解页面速度的运作原理,并且你该为网站采取哪些措施。
话虽如此,如果你并不是技术人员而只是希望透过安装插件/模块来加速你的网站,这里有一些能帮上忙的:
WordPress:
WP Rocket (付费版) + 一个图片优化插件,或者:
Autoptimize + 一个缓存插件
Drupal
让我们开始吧。
页面速度是加载网页所需要的时间,很难为页面速度指定单一的数字。因为有多种指标,这些指标又会以不同的方式、目的及测试条件去捕捉页面加载的元素。
最近随着移动端速度成为排名因素,Google 重新开始关注页面速度,像是 Google Search Console 中的速度报告、Chrome 宣布他们可能会标记速度较慢的网站等,但你知道页面速度自从 2010 年以来一直是 Google 的排名因素吗?
下面是你应该关心的原因:
- 影响用户体验,你希望访客获得快速流畅的体验,但任何的延迟或滞后都是显而易见的。
- 影响分析,一般来说更快的网站会记录更多的访客,因为分析标签会更快加载。如果一个人在标签被触发之前离开,他们将不会被记录在分析系统中。
- 搜索引擎优化?根据官方公告,速度更新仅影响速度最慢的网站。
许多研究表明如果提升你的网页速度,你会看到更多的自然流量、广告点击造访率、更多访客等好处,WPO Stats 有许多对网页速度提升的实例研究。
然而我要提醒的是,这些研究可能有点误导,除非你之前速度非常慢,否则 Google 表示提高页面速度不应影响你的排名。
那么为什么你会看到更多的访客呢?
答案是分析标签可能比以前更早触发,并且能够在他们离开页面之前能记录更多人。
没有一个标准门槛,常见的建议之一是你的网站应在三秒内加载完成。这可能是因为来自谷歌的一项研究,其称 53% 的移动端访客会在加载时间超过三秒后离开页面。
此建议也很可能基于速度指数(Speed Index)指标,我们将在稍后讨论到,但这只是我基于研究当前流行指标的推测。我不相信谷歌在给出页面速度的数字时提到过任何一项特定的指标。通常 Google 代表性的建议都很笼统,例如『为用户打造快速的网站』或『让网站尽可能更快速』。
要了解如何改进你的页面速度,你要先理解浏览器如何建构页面,为此我们将主要查看瀑布图以观察哪些资源正在加载。你还可以在浏览器加载时,透过『右键单击』> 检查 > 网络(Network)查看。
建立连接
下面的绿色、橙色及紫色代表与网站建立连接所需的时间,我将介绍下面的每种颜色及其代表的含义。
DNS (绿色)
域名系统 (DNS) 被视为一个网络电话簿,你为浏览器提供一个网站名称,它会与 DNS 服务器进行检查以获取 IP 地址(位置标签),告知该网站的托管位置。就像在手机上存储联系人一样,你只需知道姓名而不是电话号码。
大多数情况下,你的 DNS 会与你的域注册商(购买域的地方)或你的内容分发网络 (CDN) 相关联。
重要的是,并非所有 DNS 提供商都是平等的。如果每一毫秒对你来说都很重要,你可能会需要考虑使用不同的 DNS 提供商。根据 DNSPerf 指出,Cloudflare 的平均查询速度为 12.6 毫秒,而其他像是 GoDaddy (46.04 毫秒)和 Rackspace (90.38 毫秒)的平均查询速度较慢。但是这些数字并不是完全准确,因为当你访问过网站后,DNS 可以进行缓存(暂时存储)在浏览器中,它的缓存时间称为 TTL (Time to Live)。 当缓存仍处于激活状态时,浏览器无需连接到 DNS 服务器即可知道从何处访问该网站。
连接 Connect (橙色)
这是浏览器与托管服务器建立连接的地方,传输控制协议/互联网协议 (TCP/IP) 很复杂,但你只要想想如何运作的就好。它可能不是一条直线,你必须转弯并且遇到人流量较大的区域。你甚至可能会改变路线或做出一些错误的转向。这就是它的工作原理,它在你的浏览器及服务器之间来回。
如果连接时间很长,这可能是众多问题之一。在不稳定的连接上,可能会发生数据包丢失并且必须重新发送的状况,这类似于你错过转弯处而不得不再绕一圈。问题也可能与透过网络发送的请求有关,需要走多少步走多远、网络上有多少其他流量类与此类似、需要转多少圈、离工作有多远、还有多少其他的汽车在路上还可能会减慢你的速度。当然还有服务器上的速率限制和连接容量,这类似于一次只允许这么多汽车通过的隧道。
许多 CDN 可以做到通过缩短与服务器的距离并使用更智能的通道,以此来解决这些问题。通过遍布世界各地的服务器网络,访问者通常可以连接到最近的服务器。一些 CDN 提供商还管理大量的网络请求,并且可以实时查看可能存在瓶颈(流量)的位置。如果他们看到更快的选择,他们可以重新安排流量路线 — 就像 GPS 在交通拥堵时为你重新安排路线一样。
安全套接层 Secure Sockets Layer (SSL) (紫色)
对于建立安全连接(HTTPS)的站点来说,就是浏览器和服务器符合 TLS (安全网络传输协议)的协议版本、密码套件(安全级别)和验证证书(以确保站点是他说安全就是安全的)。
你可能会想只要不使用 HTTPS 就能使你的网站更快,部分来说是对的 — 至少对于连接部分是这样。但是使用 HTTPS 还有其他好处,例如浏览器不允许你在没有 HTTPS 的情况下使用 HTTP/2 (H2)。H2 有一些优点,比如能保持连接,所以它不必为同一服务器上的文件不断打开新连接。而这些请求中的标头也比 HTTP/1.1 中的小,并且可以同时传输多个文件。在大多数情况下,使用 HTTPS 和 H2 的站点将比使用 HTTP 的站点更快。
通常你能获得的最显着收益来自升级你的协议(例如 TLS 1.3 比 TLS 1.2 快)和实施 HTTP 严格传输安全 (HSTS),它告诉浏览器始终使用安全连接。浏览器将请求从 HTTP 更改为 HTTPS,而无需联系服务器进行重定向。在下图中,使用 HSTS 将消除从 HTTP 到 HTTPS 的重定向以及所花费的时间。
你甚至可能会考虑使用 HTTP/3 实现更快的连接。然而对该协议的支持还处于早期阶段,至少在本篇文章撰写时还不是一个可行的方案。
考虑到这一点,使用 3G 连接速度较慢的中档智能手机连接到网站大约需要 2 秒。在有 LTE 连接的同一部手机上,时间能缩短到约 0.41 秒。在具有正常速度的台式计算机上,建立连接的时间则不到 0.1 秒。
请记得如果你看到更长的连接时间,可能因为测试设备的带宽或处理能力有限,这些因素与缓存都很重要。这些因素可以帮助你向那些可能有最新智能手机、已连接到 WiFi、加载页面所需的所有文件都已经缓存(我们将在另一部分讨论这一点)所有对于网站有着理想条件的人们,解释他们的体验方式,而这不是大多数人会体验到的情况。
下载和处理 HTML
页面的 HTML 代码是浏览器最初下载的代码,这是你在网站上单击鼠标右键并点击“查看页面源代码”时就会看到。一旦建立连接,并且浏览器从服务器获取回传的第一条信息,我们就会到达首字节时间 (TTFB),这是对初始响应时间的典型衡量方式。如下面的橙色线所示,这是从 HTML 请求开始(浅蓝色)到 HTML 开始下载(深蓝色)的时间。
如果 TTFB 出现延迟,有可能是因为数据库查询、服务器资源、等待服务器端渲染 (SSR) 完成或其他通常是涉及到建立动态内容的事情,下载时间将取决于连接和文件大小等因素。
这也是浏览器开始构建页面的地方,下载 HTML 时浏览器会将其解析为文档对象模型 (DOM),这是计算机理解内容结构的方式。该解析过程使用浏览器的主线程来处理用户的操作行为并绘制页面、运行 JavaScript 以及执行布局、回流及垃圾收集。现在只要知道这个主线程的存在并且处理多个不同的任务,我们稍后会详细介绍这一点。
如果你看到 HTML 和下一个请求之间存在差距,最可能的原因是 CPU 正忙于处理 HTML 以构建 DOM。因为是 CPU,这又取决于所使用的设备,因此你可以使用功能更强大的设备进行测试,来看看该差距是否仍然存在。
对于 HTML 和其他文档像是 CSS 和 JavaScript,你可以通过使用更少的代码、从代码中删除不必要的字符(如注释和空格)以及压缩文档大小来缩短时间。关键是使下载的文档更小,这样这部分的加载就会更快。但是缩小和压缩的方法不止一种,在许多情况下,这由 CDN、服务器(Apache 或 Nginx 是常见服务器)或是插件/模块/封包所处理,你可以在此处找到有关实现压缩和缩小的更多信息。
处理额外的连接
HTML 档下载之后,将开始处理对其他文件和其他服务器的相关引用,并开始新的连接,通常是将 JavaScript、CSS、图像和字体等其他文件添加混合。当某些文件引用其他文件时,事情开始变得疯狂,我们开始连接链和文件下载链。请看下方 Forbes.com 的请求地图,每个点都是一个单独的文件请求,每一行都是一个文件引用另一个必须下载的文件。总的来说,它是跨越 128 个连接的 363 个请求。
尽可能使用相同的服务器进行请求
过去,在与主域不同的去cookie域(cookieless domains)上托管资源是一种最佳实践方式,有时使用多个网域(称为域名分片的过程)会有益处,因为浏览器设置了连接请求限制。
从 HTTP/2 开始,这已不是最佳实践方式,如果可以应该对请求使用相同的服务器。
例如,在下面的瀑布图中使用 cdn.ahrefs.com。
如果该文件托管在 ahrefs.com 上,那么它甚至不必连接。由于建立连接、DNS 连接和协议security handshake (安全握手)的时间而导致延迟。如果没有额外的跳转,我们能更早地获得文档,这意味着页面加载速度会更快。
虽然自托管许多文件(如字体)可以带来收益,但可能还有其他的权衡,例如缓存(存储文件的副本),因为浏览器有时可能会缓存共同资源。例如,如果我访问了一个从 Google Fonts 调用字体的网站,然后又访问了另一个使用相同字体的网站,我可能已经在本地缓存了该文件所以不必再次下载。
使用 Preconnect 或 DNS-Prefetch (如果你使用另一台服务器)
如果要使用不同的服务器,请预先连接到包含页面加载早期所需文件的服务器,这将比正常情况下更早地连接到另一台服务器。请参阅下面亚马逊的其中一个连接是如何在 HTML 完成处理之前启动。
代码范例:
<link rel="preconnect" href="https://site.com">
如果只想尽早处理连接的部分,那还有 DNS 预取(DNS-prefetch)。绿色(DNS)部分会提前连接,但其余的连接部分将在稍后发生。DNS-prefetch 比 preconnect 有更好的技术支持,但是如果查看当前的使用统计数据,差异几乎可以忽略不计。如果知道需要尽早加载来自该服务器的某些内容使页面正常运行,则预连接通常会更好。但是由于预连接需要更多的路由和安全工作(橙色和紫色),所以它在早期也会占用更多的资源。
代码范例:
<link rel="dns-prefetch" href="//asset1.com">
浏览器如何渲染页面
在我们继续讨论优化选项之前,我认为最好先了解一下浏览器如何渲染页面。我们现在有其他文件像是 CSS、JavaScript、图像和字体,浏览器必须将它们与 HTML 一起转换成有用的东西。随着新文档的引入、下载、解析,这会是一个动态过程,并不断重新排列内容以构建页面,这个过程通常称为关键渲染路径,它看起来像这样:
- HTML被处理成我们前面提到的DOM树
- CSS 被解析为 CSS 对象模型 (CSSOM),它告诉浏览器所有内容的样式、填充、颜色、大小等
- CSSOM 与 DOM 一起构成了所谓的渲染树
- 根据渲染树中的内容,来处理每个元素在浏览器视口中的位置来布局
- 像素被绘制在屏幕上,因此你看到的不是白色屏幕,而是颜色、形状、文本和图像。
目标应该是尽早获得所需的元素,以尽可能快地构建初始视图,可视加载时间是人们对页面速度的感知视图,也就是内容在他们屏幕上出现的时间。对此影响最大的是资源的加载方式,通常由 CMS 或 JavaScript 框架负责帮助浏览器确定何时/什么/如何加载资源的优先级,以使站点显示得更快,稍后会详细介绍。
你还希望在布局阶段保持简单、避免复杂的计算和大量更改,谷歌在这里有一个以开发人员为中心的指南,还有另一个关于简化绘制过程复杂度的指南。
视觉加载指标:
- 首次绘制 First Paint (FP) — 浏览器首次渲染任何东西
- 首次内容绘制 First Contentful Paint (FCP) — 浏览器从 DOM (文档对象模型)渲染一些东西,可以是文本、图像等
- 首次有效绘制 First Meaningful Paint (FMP) — 加载视觉上最重要的元素
- 最大内容绘制 Largest Contentful Paint (LCP) — 折叠上方加载的最大元素
- 视觉完成 Visually Complete — 页面视觉化加载
- 速度指数 Speed Index — 考虑到多个时间点视觉加载的计算得分
- 累积布局偏移 Cumulative Layout Shift (CLS) — 测量加载期间元素在视口中移动程度或是布局的稳定性。这里有一个很好的指南关于造成 CLS 的形成。
“If you can’t measure it, you can’t improve it” https://t.co/dNrDYaIj4Z covers how to measure modern web performance metrics & optimize for them. pic.twitter.com/yvBO2FQ6Ds
— Addy Osmani (@addyosmani) January 31, 2020
推文译:“如果你不能衡量它,你就无法改进它” http://web.dev/metrics 介绍了如何衡量现代网络性能指标并优化他们。
通过瀑布图查看视觉加载
在 WebPageTest 的摘要(Summary)部分,如果你启用录制,那么主表中应该有一个带有“Filmstrip View”的视频列。在该视图中,顶部带有视觉快照的红线与底部瀑布图的红线位于同一点。
通过将红线移动到视觉加载图中的不同点,应该能够查看刚刚加载到瀑布图中的内容,它允许不同元素在视觉显示呈现,这可以帮你确定可能需要优先处理哪些文件。
例如,如果你看到除了文字之外页面大部分都已加载,但紧接着加载了字体并显示了文字,那很好的表明需要使用字体来显示文字。你还可以简单的查看生成的屏幕截图来判断不同视口下可能需要哪些图像。
此图表底部是其他信息,例如 CPU 使用率、带宽、浏览器主线程上的活动和交互性,所有这些图表再次说明取决于设备和连接类型,这些信息可用于帮助解决不同的问题。例如,或许只是下载的太多了,使带宽保持在最高点;或者说可能某个脚本将所有 CPU 用于特定设备,这可能会导致延迟。
文件类型 CSS
页面速度变得复杂的地方在于,在许多情况下没有绝对一个正确的方法。大多数方法都需要权衡,有些方法实现和维护更复杂。必须决定在你的情况下什么是最简单、最快和最适合你的。
查看 CSS 文件,默认情况下它们会阻止渲染,这意味着在页面向用户显示内容前需要下载和处理它们。如果你缓存(存储文件的副本,本文稍后将介绍),则该文件可以重复用于后续页面加载。意味着它不必再次下载,并且下一次呈现的更快。
大多数速度工具进行测试都是第一次查看,因此在 PageSpeed Insights 等工具中看到的很多内容,都代表首次用户查看的一个页面,而不是访问多个页面或经常返回你网站的用户,你的目标应该是优化用户第一次访问的呈现及后续访问上的呈现。
异步加载 CSS
你希望尽快加载重要的代码,我们将在稍后讨论一些选项,但另一部分是你希望 CSS 不要阻止渲染。为此,我们希望将之后加载过程中需要的样式表(stylesheets)加载为不同的媒体种类,然后将其应用于所有类型。 通过滥用浏览器处理特定链接元素属性的加载方式来欺骗浏览器,它要做的是在不阻止渲染的情况下加载 CSS (因为在这种情况下,我们告诉浏览器这个样式表是用于打印输出的,而不是真正用于这个版本的页面),然后在加载后应用到所有媒体类型(那些不打印输出的)。
例如:
<link rel="stylesheet" href="/my.css">
变成:
<link rel="stylesheet" href="/my.css" media="print" onload="this.media='all'">
你可以将其与所有 CSS 引用一起使用,权衡的点是用户可能会遇到一些闪烁/重新设计样式(flashing/re-styling),因为某些页面元素可能会在应用 CSS 之前被绘制。 因此当应用 CSS 时,可能会改变显示内容的位置和方式。
内联 Inline
内联获取在首屏呈现内容所需的代码,并与 HTML 响应一同交付,而不是只有单独的文件,这通常是缩短渲染初始视图所需时间的最快方法。
考虑这个问题最简单方法是直接将 CSS 和 JS 文件的关键部分放入 HTML 中,最初的 HTML 需要更长的时间下载和解析,但页面甚至可以在所有其他文件下载之前即时呈现。
内联可能会在初始页面加载时获得最快的渲染,但传统的权衡方式是透过缓存。加载在 HTML 中的代码无法从缓存中重复使用,因此通常部分代码会加载两次:一次是 HTML,另一次是在正常情况缓存的文件中。但是如果为每个页面内联代码,这也意味着后续页面也会有额外的代码。这是涉及 service workers 使用的个高级设置,但可以同时使用内联和缓存,结合我们上面提到的使 CSS 的其余部分异步加载,这几乎是一个理想的状态。
请记得,可以缩小内联 CSS 代码,如上面 HTML 部分所述,这会删除一些不必要的间距和额外字符,使代码更小、下载速度更快。
你可能不想内联每份文件的所有内容,可以详细考虑后并只内嵌关键内容。从技术上讲,可以内联所有 CSS 和 JS 甚至字体和图像,但最终会下载成一个巨大的 HTML,其中很多代码都没有使用,这实际上会使网站变慢。如果有一些只有几 KB 的小文件,并且想要为它们内联整个文件,那么这样没问题。
大规模内联关键 CSS:
你需要一个自动化系统,而不是对每个页面都这样做。仅内联 WordPress 主题主页的 CSS 是有意义的,因为它通常与其他页面是不同的样式表。通常会有一些插件/模块/包、一个关键版本或关键 CSS。这些包(packages)可能适用于你使用的任何 taskrunner 或是如 Grunt、Gulp、Webpack 或框架如 React、Angular、Vue,你还可以找到特定关于 WordPress 或 Drupal 的教程,甚至是手工编码的页面。他们将向页面发送一个无头(headless)浏览器,以确定哪些 CSS 对于不同大小的页面加载上是关键,并为你提供代码或将代码拆分为关键和非关键元素,以便可以适当地加载它们,举几个例子:
Grunt:
https://github.com/filamentgroup/grunt-criticalcss
https://www.npmjs.com/package/grunt-critical-css
https://github.com/bezoerb/grunt-critical
Gulp:
https://github.com/addyosmani/critical
https://www.npmjs.com/package/gulp-critical-css
Webpack:
https://github.com/anthonygore/html-critical-webpack-plugin
https://github.com/GoogleChromeLabs/critters
https://github.com/anthonygore/html-critical-webpack-plugin
https://www.npmjs.com/package/critical-css-webpack-plugin
React:
https://www.npmjs.com/package/react-critical-css
https://github.com/addyosmani/critical-path-css-tools
https://github.com/sergei-zelinsky/react-critical-css
Angular:
https://github.com/addyosmani/critical-path-angular-demo
Vue:
https://github.com/anthonygore/vue-cli-plugin-critical
https://vuejsdevelopers.com/2017/07/24/critical-css-webpack/
Drupal:
https://www.fourkitchens.com/blog/article/use-gulp-automate-your-critical-path-css/
WordPress:
https://joe-watkins.io/javascript/inline-critical-css-with-wordpress/
https://wordpress.org/plugins/wp-criticalcss/
Hand-coded:
https://www.sitelocity.com/critical-path-css-generator
https://jonassebastianohlsson.com/criticalpathcssgenerator/
预加载 Preload
如果不打算内联关键 CSS,那么下一个最佳选择可以是使用预加载 Preload。预加载在加载早期提早获取请求,比平时更快地获取显示页面所需的基本资源。Preload 将预加载资源在浏览器优先级设置为高并异步加载它们,因此它们不会阻塞渲染,也适用于跨域。
浏览器为每个文件请求提供优先级,这个想法是让需要的文件更早地显示在折叠内容之上(以更高的优先级),并将那些不需要的文件推迟到流程的后期。可以在 Chrome 开发工具的网络页签(Network)中查看文件的优先级。只需右键单击选择优先级,然后将其添加为列。
它会获取一个可能在稍后开始下载的文件,并尽快下载它。同样的,另一个好处是预加载的文件本来会在渲染前被阻塞,现在不再会这样了。
New: Loading performance pro-tips for <link rel=“preload”>, prefetch & priorities in Chrome: https://t.co/PzgxSQEgcd 🔥 pic.twitter.com/DpDmlNIedO
— Addy Osmani (@addyosmani) March 27, 2017
推文译:“新增内容:Chrome 中对于 <link rel=“preload”>、预取(prefetch)及优先级专业提示:https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf”
结合我们上面提到的使 CSS 异步内容,预加载只是添加了另一行代码,目的在通过将浏览器优先级设置为高的情况下,来更快地获取文件,这也适用于不支持预加载的浏览器。
代码范例:
<link rel="preload" href="/my.css" as="style">
<link rel="stylesheet" href="/my.css" media="print" onload="this.media='all'">
选择要预加载的文件
通常你会拥有包含大量网站 CSS 的主要主题文件,开发人员通常会以主题命名或以“风格”称之,或是有时以网站本身命名。如果你无法识别此文件或认为可能还需要预加载其他文件,那么最简单的检查方法是使用 Chrome Dev Tools 中的请求阻止功能。打开 Network 页签并加载一个页面以查看请求的文件,你可以右键单击这些将它们添加到阻止列表中。当你重新加载页面时,如果页面看起来仍然正常,那你可能没有阻止到上述折叠内容所需的文件。当你获得一个外观破损的页面时,这表示该文件是需要呈现在折叠上方的内容并且是你想要预加载的文件。
关于预加载的注意事项
- 需要对字体进行跨域处理(crossorigin),否则文件会被双重加载
- 仍然需要 JS + CSS 的普通文件调用,所以不要删除它们
- 即使在另一个文件(如 CSS 文件)中调用字体,你也可以预加载该字体
- 注意你预加载了多少,你可能会在预加载过多文件时遇到问题
服务器推送
这是 HTTP/2 (H2) 规范的一部分,它允许服务器在没有被请求的情况下传送文件。 因此与『HTML > CSS > 字体』链不同的是,这允许站点说我需要该字体,就只需发送字体。
服务器推送是有问题的,我通常建议不要使用它,但是如果你是一位出色的开发人员或可以访问它,或许可以试一试。它在与页面请求相同的连接上从服务器请求文件,服务器推送可以加载两次资产。有一个使用 cookie 并检查是否已经将资产推送给用户的解决方法,但实现方式很复杂。还有另一个涉及连接的问题,可能导致文件根本无法加载。通过所有额外的工作,你可能仍然看不到预加载的显着收益,因为浏览器会在推送缓存之前检查页面缓存(预加载所在的位置)。
文件类型 JavaScript
JavaScript 也可能很复杂,有很多选择和考虑因素。有时用于提供功能、有时用于拉入主要内容、有时甚至用于修改 CSS。此外,某些代码可能需要其他代码才能正常运行,这些被称为依附性,改变 JavaScript 的加载方式可能最终会破坏页面的某些功能。
如果 JavaScript 在页面的内容或样式中扮演关键角色,或是如果它是核心系统,就像许多 JavaScript 框架的情况一样,那就内联和预加载而言,许多与 CSS 相同的规则都适用。但是你还是可以选择服务器端渲染 (SSR),这会处理代码并渲染出快照。举例来说,如果使用 JavaScript 填充页面上的项目或用于菜单,你可能希望在加载的早期获取此信息或减轻客户端浏览器的一些负担,那你可能需要使用 SSR 的解决方案。
查看页面是否需要 JavaScript 的最简单方法是点击 Chrome 中的挂锁并打开站点设置。你将看到一个权限列表,其中一个是 JavaScript,可以在其中允许或禁止它。阻止 JavaScript 并重新加载页面,比较使用和不使用 JavaScript,应该会显示页面中是否缺少任何元素。如果缺少某些内容,请重新启用 JavaScript 并执行与我们上面使用 CSS 相同的阻塞过程(blocking process),以找出哪些文件对呈现的内容至关重要。
移至页脚
对于内联脚本可以考虑将它们移至页脚。请记住,JavaScript 会阻塞解析器,这意味着它会阻止读取 HTML。将这些脚本移至页脚可确保在发生任何阻塞之前可以处理大部分数据。你还有其他可能更好的脚本引用选项,例如延迟和异步。
延迟/异步
延迟和异步是可以添加到脚本标记的属性。通常正在下载的脚本在下载和执行时会阻塞解析器。异步加载将让解析和下载同时发生,但在脚本执行期间仍会阻塞。延迟加载不会在下载过程中阻塞解析,并且仅在 HTML 解析完成后才执行。
延迟/异步代码示例
正常:
<script src="https://www.domain.com/file.js"></script>
异步:
<script src="https://www.domain.com/file.js" async></script>
延迟:
<script src="https://www.domain.com/file.js" defer></script>
Addy Osmani 对阻塞、异步、延迟和预加载以及它如何影响浏览器优先级进行了很好的细节说明。
JavaScript loading priorities in Chrome: https://t.co/O3Qg2eHgXb ~ how do <script>, <script defer>, <link rel=preload> + <script async> & friends load then execute? A handy table. pic.twitter.com/ocw9iLWZA8
— Addy Osmani (@addyosmani) February 20, 2019
推文译:Chrome 中的 JavaScript 加载优先级:https://bit.ly/loading-priorities 如何做 <script>, <script defer>, <link rel=preload> + <script async> 加载并执行?一个便利的表格。”
响应能力
响应度通常由首次输入延迟 (FID) 来衡量,这是从用户与页面交互到它可以响应的时间。Max Potential FID 是用户可能遇到最坏情况的 FID。许多人通常会测量可交互时间 (TTI),即页面完全交互所需的时间。
还记得我们之前提到的发生在主线程上的事吗?好吧,只有一个主线程,JavaScript 会争夺这些资源。当线程被阻塞时,它无法响应用户的输入,所以页面感觉很慢。当用户点击并且页面没有立即执行他们要求的任何操作时,他们会感到延迟。发生这种情况时,用户可能会以一种不好的方式让你知道。
影响响应能力的是 JavaScript,为了让它可以完成的所有不同事情,加载的所有 JavaScript 都必须在同一个地方运行。
上图是主线程的样子,Chrome 开发工具中 Performance 页签中的那些红色刻度线表示可能存在问题的地方。 通常在主线程上运行太多时间的任务就会被标记,每一个地方都是超出页面负荷的工作并且无法及时响应用户输入。
当任务正在运行时,页面无法响应用户输入,这是感受上的延迟。任务越长,用户经历的延迟就越长。任务与任务之间的中断是个机会,页面必须切换到用户输入的任务并响应他们想要的内容。
第三方标签
这是可以在 PageSpeed Insights 中找到的另一份报告,它显示了第三方脚本阻塞主线程与影响交互性的大小和时间。
请注意,尤其是对于标签管理器,有些可能是标签管理器的问题而不是脚本的问题。它可能是标签管理器计数容器中脚本的一部分,并且没有正确计入第三方脚本。
使用大小和主线程时间来确定可以摆脱的内容。请记住,大多数第三方脚本都会添加某种功能、跟踪或定位,但它们很少是页面正常运行所必需的,使用你的判断力来确定获得的数据是否值得为这些脚本增加额外的加载时间。
JavaScript 膨胀的常见来源:
- Jquery
- A/B 测试系统
- 热图系统
- 真实用户监控(RUM)系统
- 在线聊天系统
清理选项:
- 使用更少的追踪/脚本。这可能是一个艰难的决定,因为营销人员喜欢数据,但有时收集的数据量只是个笑话
- 合并具有相似功能的系统,就像你正在运行多个分析系统或多个具有用户信息的系统一样,许多程序具有多种功能,有时最终会得到与另一个具有相同或相似功能的脚本,而可能不需要其中一个。
- 区隔。例如,一些 A/B 测试系统会存储并强制加载系统中当前的所有测试列表,从而增加下载的大小。很多时候,可以按站点的部分进行区隔并创建较小版本的文件。
- 服务器端跟踪而不是客户端。有一些权衡方式可以达到这种追踪方式,我不在这里展开介绍,但是你可以找到很多资源讲述为什么使用这种方法而不是别种。
- 使用 Web Worker 将处理(processing)移出主线程。这样做的缺点是 web worker 无法访问 DOM,这也是相当进阶的,需要熟练的开发人员。
- Service Workers / Edge Workers。我对这项技术的未来感到兴奋,它基本上允许 JS 在 Edge (或 CDN 级别)而不是在客户端浏览器上运行。所以之前对于 A/B 测试系统来说,可能是下载一个文件,然后在客户端浏览器上处理和执行。因为测试可能会覆盖部分 DOM 并在加载后期发生,所以可能会看到随着测试的变化而出现的视觉变动。现在基本可以对将要进行的改变进行预处理,并将它们与交付给机器人和用户的 HTML 进行内联,一些平台已经在使用这个方法了。
- 简单地延迟文件加载的执行。 如果不需要立即使用或仅根据点击等操作发送文件请求。例如,在页面加载的前五秒内可能不需要实时聊天系统,因此请将它延迟。你还可以在有人悬停或点击按钮后请求该文件,因此它根本不会与初始页面一起加载。或者使用带有播放按钮的图像而不是嵌入 YouTube 视频,并且仅在用户点击时 YouTube 视频元素时加载并播放内容。
译者注:Web Worker 是由 W3C 以及 WHATWG 共同制定出来的API,让 javascript 可以独立在背景执行而不会影响到原来的HTML页面。
JS 框架的好处:
像 React、Angular 和 Vue 这样的 JavaScript 框架比传统系统有优势,像是
- 摇晃树(Tree shaking):仅提供页面上使用的代码,任何不需要的附加文件或代码都不会加载,因此会让文件和页面更小,它消除了传统上为其他每个页面和可能需要的代码。
- 代码拆分(Code splitting):将文件拆分的更小块,因此有更多的交互机会。例如,假设有一个 1MB 的 JS 文件,它在主线程上作为一个长任务运行,并在它运行时阻塞了交互,你可以将其拆分为 50KB 的一块,这样任务就不会运行那么久,并且更短的时间段之间有更多的空间,页面可以更快的响应用户输入。
字体档案
对于字体,有许多与我们之前提到相同的选项(像是内联或预加载所需的字体)。如果你想照此方法,你会在这里找到一些预加载字体的代码示例。但是对于字体,我建议使用的是 font-display: swap;,它到自定义字体准备就绪前都使用默认系统字体,然后交换自定义字体,这在样式表中相对容易做到。
@font-face {
font-family: 'Whatever';
font-display: swap;
}
如果使用 Google 字体那就更简单了,需要做的就是添加&display=swap
作为 URL 中的参数。
<link href="https://fonts.googleapis.com/css?family=Whatever&display=swap" rel="stylesheet">
图片档案
图像的主要问题是大小和面积,你需要优化的图像为设备加载正确的尺寸和面积。
图像是异步加载的,因此它们不会阻塞页面加载,但它们会增加交互的面积和总时间。
另一个潜在问题与优先级有关,其中某些图像可能没有正确优先级或优先级高于 CSS 和 JS 等关键文件。我不会详细介绍这一点,但你可以在此处和此处找到更多详细信息以及有关如何进行故障排除等信息。另一种是存在太多图像加载的情况,将带宽等资源占满并降低整体页面加载速度。
我们讨论过的很多东西像是内联和预加载都适用于图像,但也有相同的权衡比如缓存或复杂性。首要的规则是不要在主题中使用大量图像或大图像,不必在移动设备上显示巨大的背景图像,人们不一定需要这些。如果必须显示图像,我推荐预加载,本指南对此有非常完整的介绍。
https://images.guide/ 上有一个很好的指南,涵盖了图像和不同格式的优化。
始终以可规模化的方式进行图像优化,有很多选项可以在不同级别执行此操作,像是 CDN、服务器、CMS、API 等,以下提供一些选项:
图像优化 CDN:
图像优化 APIs:
图形用户界面(GUI):
命令行:
如果你使用 webpack、gulp 或 grunt,Imagemin 也有一个 npm 模块
JPEG:
PNG:
GIF:
WordPress/Drupal
我没有什么特别的建议,你会发现 WordPress 和 Drupal 有很多选项。
延迟加载图像
如果有人告诉你他们需要“推迟屏幕外图像”,这就是你需要的。它基本上是延迟加载不超过首屏的图像,因为它们还不需要。一旦用户开始滚动,图像就会加载进来。
我会说你想要使用 IntersectionObserver 的程式库,但由于浏览器支持问题,它可能会有一个 polyfill。最流行的程式库是 lazysizes,但你会发现许多设置选项。
译者注:polyfill 是一个代码片段(通常是一段js代码)或者插件用来为旧浏览器提供它没有原生支持的较新的功能。
从 Chrome 76 开始浏览器已经引入了延迟加载,我希望很快会有更多浏览器这样做,但就目前而言,我们可能希望对 Chrome 使用这种方法,并为其他浏览器添加 polyfill,你可以在此处找到更多信息。WordPress 在 5.4 版本中默认添加了延迟加载。
响应式/调整图像大小
为正确的屏幕提供正确的图像,加载大图像然后将其缩小只会浪费时间和资源,同样的这也有很多自动化解决方案。例如很多 CDN 会处理它,也有像 sharp npm 包、ImageMagick CLI tool 或不同系统的各种插件/模块之类的工具。
更改图像格式
像 webp 这样不同的格式可能会更好,但由于浏览器存在支持问题。要么必须进行大量检测,要么使用为你执行此操作的服务。有很多指南,但我不建议大多数人解决这个问题,除非你能找到一种简单、自动化的方法。
页面尺寸/大小
这是所有资源的总和,较小的页面速度更快。我们已经讨论了许多改进,例如缩小、压缩和简单地删除任何未使用的东西。页面最初加载的东西越少,页面显示的速度就越快。
目标应该是以最少的数据量尽可能快地加载首屏内容。然后你可以加载页面上所需的其余信息,同时保持所有内容尽可能小。问题通常来自未使用的代码、图像或网站功能及工具导致的大小膨胀。我将这部分留给你自己思考的原因是,你应该考虑页面所需使用的数据总量。
查看我的网站成本以了解用户加载你的页面的大致成本。
其他网络性能的机会
你可以通过多种方式提高页面速度,我将介绍一些更重要的内容,但除此外还有更多优化的机会,因为页面速度是一个非常复杂的话题。
缓存
缓存只是文件的存储副本,缓存文件可以在下一页重复使用,无需再次下载。
服务器缓存
这是浏览器请求文件时文件的来源,理想情况下,你希望给离用户最近的缓存。我的意思是,缓存可以存储在许多不同的级别,并为每个级别设置不同的 TTL 以设定缓存何时过期。在长时间缓存和快速更改并更新内容之间取得平衡,这不是那么简单,因为可以在进行更新时通过不同级别清除缓存,这是与缓存预热系统一起执行此操作时的理想方法。缓存预热系统会发送一个机器人来重建缓存,而不是等待用户请求文件,这意味着用户无需等待初始缓存的构建。
检查方式通常像是:CDN 缓存 > 服务器缓存(如 Varnish) > 原始页面(必须即时构建页面)。 通常,像 CDN 这样更高级别的缓存会更快,因此你会希望大多数点击都在该级别。
举例来说,下面显示 Cloudflare 中我的一个站点,CDN 级别缓存命中率略高于 50%。不幸的是,这意味着许多请求不是由 CDN 提供的,而是必须返回到服务器级别的缓存。或者说如果那里没有当前的缓存版本,它将不得不动态构建页面,这会使用大量数据库资源并拖慢用户速度。
浏览器缓存
即使你有一个在页面速度方面测试不佳的大型网站,页面的第一次和第二次加载或页面之间的导航也可能存在相当大的差异。到目前为止,我们讨论的很多内容都集中在使初始加载更快,这是大多数测试工具看到的内容,也是用户对网站的第一印象。当用户访问一个页面时,浏览器可以在该人的计算机本地缓存许多文件,这些文件可以重新用于后续的访问。
举例来说,查看 Ahrefs 第一次和第二次加载之间的差异。大多情况必须在第一次加载时下载的文件都缓存在客户端(浏览器)上,这意味着第二次加载可以重新使用已经下载的文件来构建页面。减少连接时间和下载意味着页面加载速度明显加快,在这种情况下,第一次绘制(First Paint)在第二次加载中的速度大约快了两倍。
第一次加载:
第二次加载:
你会看到在 Lighthouse 等工具中标记的缓存问题为“使用有效的缓存策略提供静态资产。”设置缓存的时间长度因系统而异,但通常需要做的是使用 Cache-Control HTTP 响应头,max-age 是你希望它以秒为单位存储的时间,可以设置为:Cache-Control: max-age=31536000
Varvy 在不同服务器设置缓存控制上有个值得一读的指南。
设置性能预算(或许)
性能预算是透过设置一个自我强加限制的方式来影响性能指标。它可以是某种类型文件的数量、大小或我们讨论过的一些速度指标。设置预算有助于整个流程的启动与进行,在此处了解更多信息。
自动调整加载
自动调整加载的方式会调整加载的内容以及何时以更加渐进的方式加载站点。首先加载具备功能性的项目,然后根据 CPU、内存或网络速度等因素加载其余的项目。因此,可用资源较少意味着可能会提供站点的精简版本,但拥有更多可用资源的人将获得更完整的体验。
其中一部分是网络信息 API,它提供有关用户连接的信息。你可以根据传入请求的网络信息更改图像/内容或执行像是关闭视频之类的操作,许多图像 CDN 都使用网络信息 API 来执行此操作。
使用其他资源提示
预取 Prefetch
Prefetch 是一种资源提示,可以在需求出现之前获取文件,这可以用于整个页面、脚本或 CSS 文件。使用它的最佳方法之一是用 Guess.js,它使用预测性预取。Guess 连接到你的分析上并根据当前用户行为获取最可能是下一页的资源。
预加载 Preload
我们已经讨论了一些预加载,但这是一个略有不同的案例,可以根据用户是否将鼠标悬停在当前视口内的链接来对内容预加载资源。虽然这可能会占用大量资源,但它可以确保加载的下一个页面会显示得更快。
AMP
AMP 在 SERP 中预加载,因此网站的一部分在点击之前就已经加载,AMP 的优势是在点击之前完成页面的视觉加载,来自搜索结果的 AMP 看起来比一般网页更快,因为页面的可见部分已经加载完成。
AMP 中还有许多其他性能增强和文件大小限制值得考虑。 尽管如此,它仍然是另一个需要维护的系统,并且在深入研究之前你可能需要考虑其他一些权衡。
实验室数据与现场数据
实验室数据:特点是可控环境、设置及可重复过程,PageSpeed Insights 就是一个很好的例子。测试在相同环境下以相同设置运行,每次运行的结果大致相同。
现场数据: 真实用户监控 (RUM) 是用户体验页面的方式,它考虑了缓存、设备、网络等所有内容,但度量和调试能力有限。
测量页面速度的工具
谷歌的工具
- TestMySite — 内含一个速度记分卡,可以在其中评估你与竞争对手的速度比较,有一个影响计算器,因此可以估计速度对你业务的影响程度,并让你建立一份报告,其中包含上述内容和一些需要特别留意的建议。
- Lighthouse (在 Chrome 开发者工具中) — 允许测试页面和应用程序的性能。
- PageSpeed Insights — 运行 Lighthouse 并提供建议,在浏览器中运行 Lighthouse 会受到许多因素的影响,例如你的计算机、网络、浏览器中的扩展程序等。PageSpeed Insights 提供了一个相当稳定的测试环境,它甚至不会像 Lighthouse 大规模设置那样使用服务器资源。
- Chrome Dev Tools — 许多有用的功能可以查看页面正在加载的内容和加载方式,例如 Network 和 Performance 页签。
- Chrome User Experience Report (CrUX) — 这是一个涵盖百万个网站的公共数据库,数据来源于那些在 Chrome 中选择愿意共享真实用户体验数据的使用者,页面速度的现场数据(来自实际用户的数据)。
- Web.dev — Lighthouse 支持的另一个 Google 测试工具,它还有一部分用于了解有关页面速度的更多信息。
其他流行的速度工具
- WebPageTest
- Sitespeed.io
- SpeedCurve
- Calibre
- Rigor
- New Relic
- Boomerang
- Batch Speed
- GTmetrix
- Pingdom
- SpeedMonitor.io
网站诊断(Site Audit) > 性能(Performance)
Ahrefs 的网站诊断工具(Site Audit tool) 也包含一些关于页面速度的信息,有一个关于 TTFB 和加载时间的报告,这是我们下载页面所用的时间。
我个人倾向于使用:
- Pagespeed Insights — 抽查单个页面。我也喜欢他们的 API,它允许每天免费进行 25000 次测试并返回许多指标,包括 CrUX 页面级的数据。我不太关注整体得分,但我知道很多人关注这总体得分指标。正如我们看到的,速度是复杂的,你可能改进了某些指标,但因为它的加权方式而没有提高分数。
- WebPageTest — 阻塞功能、幻灯片、视频、瀑布图和请求图。此外,他们的 API 用于阻塞大规模测试(还有灯塔报告)。
- GTmetrix — 串连的请求报告
- CrUX – 区域研究、直方图、竞争对手比较。
- Web.dev — 很棒的文档
谷歌使用什么数据来提高页面速度?
根据谷歌趋势分析师 John Mueller 在这段视频中的说法,谷歌使用页面的理论速度(使用实验室数据)和尝试使用这些页面的用户真实现场数据,他说这与 Chrome 用户体验报告中的数据相似。
现实情况是他们使用的数据来源尚未得到公开确认,虽然 John 没有说他们使用 PageSpeed Insights 和 CrUX 数据,但这些数据很可能代表了 Google 正在使用的数据。我最好的猜测(这纯粹是推测性的)是他们使用在渲染过程作为实验室数据(可能是灯塔,但也可能不是),并且他们可能拥有类似于 CrUX 的内部资源,用于现场数据。
估计影响的最简单方法是制作页面的静态副本,将代码复制到服务器并测试页面以获得基准指标。对页面进行更改并再次测试,应该就会知道更改造成的大概影响,因此当你在实时站点上进行更改时,就会知道大致影响的程度。
最后的想法
你应该使网站用户使用时尽可能快地,选择代表用户页面载入体验和交互性的指标并对其改进。提高页面速度没有一个特别的门槛,但通常有一个阶段,就是当你权衡后发现获得的好处可能不值得再花时间、精力、成本等潜在权衡(例如从工具中丢失数据),一般来说,我会尝试比竞争对手稍微快一点。
有页面速度的相关问题吗?来 Twitter 找我吧。
译者,李元魁,SEO 分解茶博客创始人