影响LCP的四个因素如下:
- 较慢的服务器响应时间
- 渲染阻塞的js和css
- 较慢的资源加载时间
- 客户端渲染
较慢的服务器响应时间
浏览器获取文档的时间越长,用户看到页面的时间也会越长。较快的服务器响应时间,可以直接改善每一个页面加载相关的指标,包括LCP。
可以使用 TTFB(Time to First Byte)
来测试服务器响应时间,你可以通过以下手段来优化 TTFB
这个指标。
- 优化服务器
- 使用CDN
- 缓存资源
- html页面优先使用缓存
- 提前建立第三方连接
优化服务器
你是否在服务器上运行一些昂贵的查询,占用了服务器一定的时间?或者服务器上做了一些复杂的操作,导致页面内容返回延后?分析和提升服务端代码的效率可以直接改善浏览器从服务端获取数据的时间。
比起仅仅是提供静态页面而言,很多服务端的web框架都会动态创建页面。换句话说,比起直接返回一个现存的html文件,这些框架会执行一些逻辑来创建页面。这就取决于数据库查询时间,或者UI框架创建组件的时间(比如: React)。大部分web框架都有提供性能指导,开发者可以根据指导来提升处理速度。
使用CDN
CDN全称Content Delivery Network,是一个分布在不同地域的服务器集群。如果浏览器去请求数据,CDN会选择就近的一个服务器给你返回,避免请求产生太长网络路径,甚至跨国。
缓存资源
如果你的html是静态的,每一次请求都不会变,可以服务端缓存下来,不用每次重新创建页面。通过缓存一份生成好的html到硬盘,服务端缓存可以有效的降低TTFB,最小化资源的使用。
- 配置反向代理,在应用服务器之前配置一个缓存服务器,专门提供缓存内容。
- 配置和管理云服务商提供的缓存特性
- 使用CDN来提供内容缓存,这样用户可以更近的获取到内容
html页面优先使用缓存
service worker可以实现这样一个功能,优先使用缓存页面,如果页面有更新,会将新页面缓存下来,下次启动则使用新缓存。
下图是使用service worker的一个对比:
提前建立第三方连接
发往第三方服务的请求也会影响LCP,特别是当页面关键内容依赖它们的时候。使用 rel="preconnect"
告知浏览器你的资源连接需要尽可能地快。
也可以使用 dns-prefetch
让dns解析更快。
考虑到浏览器兼容性, dns-prefetch
可以作为 preconnect
的一个fallback。
preconnect一般只配置一个,dns-prefetch可以配置多个,所以一般把最关键的资源配置成preconnect,比如js或者css所在的cdn域名
渲染阻塞的js和css
在浏览器渲染任何内容之前,需要解析html,并生成dom树。html解析器会被任何样式文件 以及同步脚本
阻塞并暂停解析。
这个不仅影响FCP,也影响了LCP。延迟加载非关键的js和css可以加速页面的主内容的加载。
减少css阻塞时间
有以下三种方法:
- 最小化css
- 延后加载非关键的css
- 内联关键css
最小化css
移除css中不必要的字符、注释和空格等,一般打包工具都会自带相应的插件。
延后加载非关键的css
使用开发者工具中的 Coverage
可以找到页面未使用的css。
优化方案:
- 移除无用的css,并将它们移到需要的页面
- 用
rel="preload"
和onload
异步加载对于初次渲染无用的css
内联关键css
将关键路径的css直接放在 中内联引用:
这样可以避免一次服务器请求,让css阻塞时间更短。
如果你无法手动添加内联样式,可以使用第三方库:
- Critical, CriticalCSS, 和 Penthouse 可以提取并内联css。
- Critters 是一个webpack插件,可以帮你内联关键css,其他css则懒加载。
减少js阻塞时间
有以下三种方案:
- 最小化并压缩js文件
- 延后未使用的js
- 尽量减少未使用的polyfills
减少资源加载时间
主要有以下几种元素会影响LCP:
元素
中的
元素元素(如果定义了封面图,会影响LCP)
- 带
url()
背景图的元素 - 块级元素带有文本节点或者内联文本子元素
有以下几种方式可以保证这些文件尽快的加载:
- 优化和压缩图片
- 预加载重要资源
- 压缩文本文件
- 自适应服务,如:根据网络条件加载不同资源(例如:4g、3g、2g)
- 用service worker缓存资源
优化和压缩图片
大部分网站的最大元素都是图片,如果能加速这些图片的加载,能有效改善LCP。参考以下步骤:
- 考虑是否必须使用图片,如果不是必须,移除
- 压缩图片(用imagemin)
- 转换成新格式(webp)
- 使用响应式图片
- 使用图片CDN
预加载重要资源
有时候,在css或者js中声明或者使用的一个重要资源可能比你预期的要晚获取,比如字体文件。
如果你明确的知道某个资源需要提高优先级,可以使用 。
Chrome73版本之后,预加载同时支持响应式:
压缩文本文件
使用 Gzip
或者 Brotli
,gzip支持度较好,brotli的压缩比更高,但只有更新的浏览器才支持。
自适应服务
当加载页面的主内容的时候,可以根据用户设备和网络条件来加载不同的资源。要完成这些,可以使用 Network Information, Device Memory, 和 HardwareConcurrency 的api。
if (navigator.connection && navigator.connection.effectiveType) {
if (navigator.connection.effectiveType === '4g') {
// Load video
} else {
// Load image
}
}
以下是一些有用的属性:
navigator.connection.effectiveType
: 网络类型,如4G等navigator.connection.saveData
: data-saver是否开启navigator.hardwareConcurrency
: cpu核数navigator.deviceMemory
: 设备内存
用service worker缓存资源
可以直接使用 workbox 的第三方库更方便的缓存资源,这是谷歌提供的一个工具库。
客户端渲染
很多网站现在都使用客户端渲染。一些框架像 angluar, react,vue等,可以方便的创建spa的单页面应用,部分替代了服务器的功能。
如果你构建的应用是以上这种方式,需要注意如果你的bundle的js文件过大,会影响LCP。如果不做优化,用户可能长时间无法看到页面,也无法交互,直到js全部下载执行完。有以下几个优化项:
- 减少关键js的大小
- 使用服务端渲染
- 使用预渲染
减少关键js的大小
与减少js阻塞时间一样。
使用服务端渲染
这样可以让内容在服务端渲染好,然后返回给浏览器,以此来减少LCP。但这种方式存在一些隐患:
- 代码复杂性太高,服务端和客户端用的是同一套js
- 服务端去执行js,并渲染html,会增加TTFB,相比于仅仅提供静态html而言
- 服务端渲染的页面看上去似乎可以交互,但实际上还是要等待js执行完成才能交互。简单地说,会增加
TTI(Time to Interactive)
使用预渲染
预渲染是一个独立的技术,主要是为了解决服务端渲染的代码复杂度。通过启动一个无头浏览器,在构建的时候就将每个路由页面渲染好并创建对应的html文件,这些文件也可以和对应的js bundles一起使用。
预渲染的方式,不能改善TTI,但对TTFB会比服务端渲染要更好些。
开发者工具
开发者工具已经可以针对实验数据进行这些指标的测试:
- lighthouse
- performance中的timeline
总结
本文只是对LCP的优化做了简单而又比较全的介绍,后面的文章会针对个别项进行单独的介绍。