Home Blogs 针对Lighthouse v6.0优化您的网站
Applications

针对Lighthouse v6.0优化您的网站

About The Author

Outline

Layer0是开源电子商务PWA框架React Storefront的主要贡献者。 今年早些时候,我们发布了许多新功能和优化 React Storefront v7其中最重要的两个是向Next.js的转变和移除了几个关键依赖项(如MobX)以支持React的新内置功能来管理状态,如useState钩子和Context API。 这导致浏览器捆绑包的大小大致缩减了一半。

这在当时是一个很好的收益,并帮助Lighthouse (v5.7)的性能得分从80年代到90年代由PageSpeed Insights (PSI)测得的典型React Storefront应用程序。 在上下文方面,Lighthouse v5.7上的电子商务网站排名前500位,83+的成绩优于99%。 我们没有意识到在接下来的几个月中,减少捆绑包的必要性,当Lighthouse v6.0会像炸弹一样下降,并消除每个人的性能分数。

了解Lighthouse分数在PSI上测得的领先电子商务网站在v6.0丢弃时的分布如何变化:

在这篇文章中,我们分享了我们如何改进React Storefront的Lighthouse v6.0得分,但这些技术可以应用于其他框架。
此外,值得注意的是,谷歌在2020年5月28日上宣布,他们将在2021年用于排名网站的具体指标。 Lighthouse性能得分将不会使用,尽管某些元素用于确定得分,即使是这样,也不会使用综合测试(如Lighthouse),而是使用Chrome用户体验报告(CUX)中的真实现场数据来测量。

Lighthouse 6.0中的新指标:TBT,LCP和CLS

Lighthouse v6.0引入了几个新的感知速度指标,并重新定义了整体指标如何影响页面的Lighthouse性能得分。
灯塔5.7 重量 灯塔6.0 重量
第一个连续油漆(FCP) 20% 第一个连续油漆(FCP) 15%
速度指数(SI) 27% 速度指数(SI) 15%
第一个有意义的油漆(FMP) 7% 最大持续油漆(LCP) 25%
第一个CPU空闲(FCI) 13% 总阻塞时间(TBT) 15%
交互时间(TTI) 33% 交互时间(TTI) 15%
- - 累计布局偏移 (CLS) 5%

总阻塞时间(TBT)

Total blocking time是Lighthouse v6.0中包含的一个新指标,用于测量解析和执行JavaScript在页面加载期间阻止主线程的时间。 此指标对现代JavaScript重载SPAS/PWA非常严厉。 即使React Storefront 7将捆绑包尺寸削减了50%,我们也发现Lighthouse v6.0产品页面的性能得分下降了20到30分,这主要是由于TBT作为影响25%整体性能得分的指标。

如果您使用的是同构框架,如Next.js,它支持服务器端渲染,TBT主要由捆绑包大小和水化时间决定。 简而言之,改进TBT的唯一方法是移除依赖关系,优化组件或通过使用更少的组件来简化站点。

最大内容绘制 (LCP)

LCP是一个新指标,其权重比Lighthouse v6.0的整体得分高出25%。 LCP旨在量化用户对初始页面加载性能的看法,方法是观察最大内容元素完成绘图所需的时间。 对于大多数网站,特别是在电子商务网站,最大的内容是英雄形象。 对于React Storefront应用程序中的商品页面,最大的内容要素是主要商品图片。 如果您不确定您的网站上有哪个元素,PSI会告诉您:

要优化LCP,必须确保映像尽快加载。

累计布局偏移 (CLS)

累积布局偏移测量初始页面加载期间页面布局偏移的程度。 布局偏移最常见的原因是图像,当从网络下载数据后,图像会在调整大小以适应图像时倾向于推动图像周围的元素。 布局偏移通常可以通过在加载前为每个图像保留空间来完全消除。 幸运的是,React Storefront的Image组件已经做到了这一点,因此React Storefront Starter应用程序拥有完美的CLS分数0开箱即用。 需要注意的是,CLS质量差的其他常见罪魁祸首是在最初绘制页面后出现的横幅和弹出窗口。 用户讨厌他们,现在Lighthouse也是如此。

我们如何优化React Storefront for Lighthouse v6.0

当我们首次使用PageSpeed Insights在Lighthouse v6.0上测试React Storefront Starter应用程序的产品页面时,它的得分在60秒以下:

为了提高得分,我们首先将目光放在LCP上。 在2.5秒时,FCP的高得令人担忧(稍后我们会讨论),但FCP和LCP之间的差距接近3秒,这是需要改进的。

优化LCP

React Storefront的主要产品图片(仅是占位符-中间带有”Product 1″的绿色框)被渲染为HTML Img标签,带有指向https://via.placeholder.com的src URL。该网站具有体面的TTFB (约150毫秒),可以通过迁移到更快的CDN (如Layer0 CDN-AS-JavaScript)来改进。 然而,我们怀疑150ms是否占FCP和LCP之间近3秒的间隔。 为了消除差距,我们使用base64数据URL (如下所示)内联图像:

				
					<img decoding="async" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAA…"/>

				
			

我们通过下载主图像,将其转换为base64,并在Layer0的无服务器JavaScript云上的服务器端渲染过程中将其填充到src属性中来实现此目的。 下面是产品API端点中的行:

				
					const mainProductImage = result.product.media.full[0]
mainProductImage.src = await getBase64ForImage(mainProductImage.src)
				
			

下面是提取图像并将其转换为基64的代码:

				
					import fetch from 'node-fetch'

export default async function getBase64ForImage(src) {
  const res = await fetch(src)
  const contentType = res.headers.get('content-type')
  const buffer = await res.buffer()
  return `data:${contentType};base64,${buffer.toString('base64')}`
return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}`
}

				
			

相当简单和古老的学校。 以及对分数的影响?

通过将LCP从5.3秒降至2.8秒,我们在该页面的Lighthouse v6.0分数中获得了21分! 这样一个小小的变化会对Lighthouse v6.0的分数产生显著影响,这有点令人不安,但我们会接受的。 应该注意的是,所有的指标在两次跑步之间有所不同,但总体得分始终保持在80年代的低水平 在上下文中,在v6.0上表现最高的领先电子商务网站得分为87 (根据PSI测得),看起来就像是90年代的样子-请查看www.rockauto.com

上面显示的FCP和LCP之间的差距与我们在几次试验中看到的差距差不多大。 大多数情况下,间隙是在100毫秒到300毫秒的范围内。 有时FCP和LCP是相同的。

优化TBT

接下来,我们尝试改善三丁基锡化合物。 这是相当具有挑战性的。 如前所述,为了改善LCP ,您需要减小JavaScript捆绑包的大小或缩短水合时间。 对于大多数应用程序,要开始淘汰依赖项以缩小捆绑包是不可行的。 使用React Storefront 7构建的应用已经受益于许多编译时优化,这些优化可以最小化Next.js的webpack配置提供的捆绑包大小,以及针对材料UI的特定Babel优化。 那么,我们在哪些方面可以改进? 水合时间。

懒惰水合赢!

幸运的是,React Storefront社区在Lighthouse v6.0停产前已经开始支持懒惰水合。 这无疑使我们加快了努力。

万一你不知道,水合是指采取行动控制在服务器上呈现的HTML元素,使它们可以成为交互式的。 按钮可单击;旋转木马可滑动等 一个页面的成分越多,水合所需的时间就越长。 复杂组件(如主菜单和产品图片传送带)需要更长时间。

惰性水合需要延迟某些成分的水合,直到它是绝对必要的,最重要的是,在初始页面加载后(和计算三丁基锡化合物后)。 懒惰水合是有风险的。 在用户尝试与页面元素交互之前,您必须确保页面元素已准备就绪,可以响应用户输入。

事实证明,在React Storefront上实施惰性水合非常困难,因为材料UI依赖于CSS-in-JS,CSS-in-JS仅在组件水合后才动态添加样式。 我将保存详细信息以备下次使用。 最后,我们构建了一个LazyHydrate组件,开发人员可以在组件树的任何位置插入该组件,以延迟水化,直到发生特定事件,例如滚动到视口的元素或用户触摸屏幕。

下面是一个我们懒惰水合的MediaCarousel示例,它显示了主要商品图片:

				
					 import LazyHydrate from 'react-storefront/LazyHydrate'
<LazyHydrate id="carousel" on="touch">
  <MediaCarousel
    ...
  />
</LazyHydrate> 

				
			

我们在几个应用领域应用了惰性水合,最显著的是:

  • 滑入式菜单:当用户点击汉堡按钮时,我们会为其补水。
  • 折页下方的所有控件:包括尺寸,颜色和数量选择器以及商品信息选项卡。
  • 主图像旋转木马:这个和主菜单可能是功能最强的组件,因此最昂贵的水合物。

以下是Lighthouse v6.0得分,应用了懒惰水合:

懒惰水合将三丁基锡化合物减少了近40%,将三丁基锡化合物减少了700毫秒(其权重比v6.0中的得分高15%)。 这使Lighthouse v6.0的总体得分提高了6分。

你会注意到FCP上升了一点,但LCP下降了。 这些小变化基本上是在运行PageSpeed Insights时”噪音范围内”的。 所有分数在两次跑步之间略有波动。

优化FCP

根据上述得分,我们认为FCP和/或LCP可能会进一步改进。 我们知道脚本可以阻止渲染,所以我们了解了Next.js如何将脚本导入到文档中:

				
					<scriptsrc="/_next/static/runtime/main-cb5fd281935517c43f30.js"async=""></script>
				
			

此处使用异步可能不是最佳选择。 如果在渲染过程中下载脚本,则会在评估脚本时暂停渲染,这会增加FCP和LCP时间。 使用延迟而不是异步可确保脚本仅在绘制文档后才被评估。

遗憾的是,Next.js不允许您更改脚本的导入方式,因此我们需要按如下方式扩展NextScript组件:

				
					import React from 'react'
import { NextScript as OriginalNextScript } from 'next/document'
/**
 * A replacement for NextScript from `next/document` that gives you greater control over how script elements are rendered.
 * This should be used in the body of `pages/_document.js` in place of `NextScript`.
 */
export default class NextScript extends OriginalNextScript {
  static propTypes = {
    /**
     * Set to `defer` to use `defer` instead of `async` when rendering script elements.
     */
    mode: PropTypes.oneOf(['async', 'defer']),
  }
  static defaultProps = {
    mode: 'async',
  }
  getScripts() {
    return super.getScripts().map(script => {
      return React.cloneElement(script, {
        key: script.props.src,
        defer: this.props.mode === 'defer' ? true : undefined,
        async: this.props.mode === 'async' ? true : undefined,
      })
    })
  }
}

				
			

然后,我们将以下内容添加到pages/_document.js中:

				
					 <NextScript mode="defer" />
				
			

令我们高兴的是,这确实提高了LCP和总体得分:

它在许多运行中也会轻微碰撞FCP,但这可能在”噪音”范围内。 然而,使用Defer vs Async时,总体得分始终高出2-3分。

总结

当Lighthouse v6.0于2020年5月下旬发布时,包括React Storefront应用程序在内的许多网站的性能得分急剧下降。 在优化之前,React Storefront Starter应用的PDP性能陷入了60年代的低谷 通过这些优化,我们把它融入了90年代低温的空气中 此时,我们认为进一步提高得分的唯一方法是消除依赖性,这可能意味着以开发人员的工作效率换取应用程序性能。

这是另一次讨论。 让我给你们留下一些我们尝试过的不起作用的东西:

Preact

Preact使捆绑包的尺寸缩小了20-30%,但Lighthouse v6.0的得分在所有指标中始终较差,即使是TTI也是如此。 我们不知道为什么,但我们知道这并不是Lighthouse v6.0的新增功能或独有功能。 Lighthouse v5.7的速度也较慢。 我们将继续定期办理入住手续,希望有一天能解决此问题。

块化

next.js最近引入了更精细的浏览器资产块化。 在Next.js 9.1中首次引入时,我们注意到额外的小块实际上使TTI变得更糟。 在新版本发布后,它可能会使应用程序更快地返回用户,因为它可以更好地利用浏览器缓存,但Lighthouse并不在意任何这一点。 因此,React Storefront已将浏览器块的数量限制在一段时间内:

				
					const webpack = require('webpack')
const withReactStorefront = require('react-storefront/plugins/withReactStorefront')
module.exports = withReactStorefront({
  target: 'serverless',
  webpack: config => {
    config.plugins.push(
      new webpack.optimize.LimitChunkCountPlugin({
        maxChunks: 1,
      })
    )
    return config
  },
})

				
			

网页字体

大多数网站使用自定义网页字体。 默认情况下,React Storefront使用Roboto (但可以更改或删除)。 自定义网页字体降低性能,简单明了。 删除网页字体,您将获得大约1秒的FCP。

与分析一样,利益相关者似乎愿意权衡绩效以使用特定字体。 React Storefront提供与网站来源相同的网页字体,以消除您从第三方CDN (如Google Fonts)加载字体时可能产生的TLS协商时间。 具体而言,我们使用NPM的typeface-roboto封装。 当与webpack的CSS-loader结合使用时,使用typeface-roboto会导致字体通过单独的CSS文件导入,浏览器需要下载并解析该文件。 我们认为将CSS内联到文档中可能有助于提高性能,但却没有。

当谈到性能时,您始终需要衡量。 理论上应该起作用的优化在实践中可能不适用。

提高您的Lighthouse得分

我们的客户在Lighthouse v6.0上排名前500位的电子商务网站中排名第95位。

安排咨询式对话!