Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

《前端体验优化》讲稿 - 2024掘金开发者大会-作者:少游 #15

Open
JuniorTour opened this issue Jun 17, 2024 · 1 comment

Comments

@JuniorTour
Copy link
Owner

JuniorTour commented Jun 17, 2024

PPT

少游_前端未来_06-27.pdf

讲稿文字版:

  1. 见下一条评论
  2. 掘金文章:https://juejin.cn/post/7386823617734426650

《前端工程体验优化实战》小册

《前端体验优化实战》
@JuniorTour
Copy link
Owner Author

这篇文章是我06-29在2024开发者大会上的讲稿,欢迎大家指点交流~

各位朋友,大家上午好!在这个大喜的日子,很高兴能给大家做一次分享。

我是少游,英文昵称 JuniorTour,在掘金和GitHub上都可以找到我。

我是《前端工程体验优化实战》一书的作者,也是Vuejs和NextJS的贡献者。

幻灯片2.PNG

在过去的6年多工作中,我做了很多很多的前端优化,有一些效果很好,得到了用户和同事的赞赏,让我收获了一些成功的经验。也有一些效果一般,但也帮我积累了失败的教训。

而且前端优化的主题贯穿了我工作过的3家公司,也让我意识到我的这些前端优化经验,也许对每一位前端工程师,业绩同仁都有帮助,所以就有了今天这次分享。

我分享的主题也就呼之欲出了,就是:《前端体验优化》 重音落在体验两个字上,这正是今天分享的特别之处。

以往我们做前端优化,往往都是专注于性能优化,目标是如何把前端项目,网站页面的性能给越做越好。

但是今天我想说,你做的前端性能优化都错了

因为性能优化不是目的,只是手段和过程。改善用户体验和开发体验才应该是我们做优化的根本目的。

性能优化和体验优化的关系,可以类比吃饭和吃饱的关系,吃饭的目的是吃饱,吃饭只是吃饱的过程和方式。

这是今天分享的目录,共分为4节。

幻灯片3.PNG

那么以往前端性能优化错在哪了呢?我们先从性能优化的一些误区和痛点开始说起。

性能优化的误区和痛点

以往的前端优化大都有以下问题:

幻灯片4.PNG

  1. 目标不明确,看到别人的优化方案有效果,就生搬硬套到自己的项目上,对自己项目的现状和痛点缺乏了解。

  2. 缺乏量化指标,拿不出客观准确的指标衡量优化效果,只能笼统的说产出是性能优化了。

  3. 只关注性能指标,没有实质性的改善用户体验,没有让用户主观上感受到优化收益。

  4. 缺乏长效化机制,无法确保优化效果长期稳定,过一段时间做的优化就被覆盖甚至删除了。

  5. 忽视了开发体验和用户体验的直接正相关性

这些误区和痛点是我过去几年切身感受到的,我相信在座的各位,做过前端优化的话也会有一些共鸣。

那么我今天要分享的主题:《前端体验优化》如何解决这些痛点呢?第一节就是:

幻灯片5.PNG

1. 无量化,不优化

幻灯片6.PNG

为什么很多人做前端优化生搬硬套,没有明确的目标?就是因为在优化前,没有进行准确的量化,没有对前端项目各方面的状况进行稳定可靠的量化,也就是没有充分了解前端项目的现状。

不了解现状,当然也就无法优化现状。

只能生搬硬套别人的优化方案,最终对用户体验的优化效果,自然也好不到哪去。

对公司内部的领导同事而言,没有准确的量化数据,也难以令人信服。无法帮助我们在商业公司内更好地表现业绩,升职加薪。

所以,优化前充分准备量化数据,是前端体验优化不可或缺的灵魂。

那么如何对前端项目的用户体验进行量化呢?

幻灯片7.PNG

常见的思路是,用lighthouse或者performance API等工具,在自己的电脑上,在开发环境,运行几次,记录下来,当做优化前数据。

这样的统计出来的数据,只是实验室数据,有很多缺点。例如

1.样本量太少,数据稳定性差。

2.数据缺乏广泛代表性,不足以体现生产环境中软硬件千差万别的真实用户体验。

我在5、6年前刚开始做前端优化时,就经常用这种方式,从本地开发环境或者内部测试环境,采集数据,用来衡量优化效果。

但我经常会发现数据的稳定性很差,今天测试页面的onload耗时是3秒,过两天就变成2.5秒了,还没开始优化,数据就波动了0.5秒,16%,波动的比例很大。

这样的数据,显然不能准确地衡量我们即将进行的优化的效果。

后来,经过多年的实践检验,我探索出了一套避免量化数据异常波动,开发体验也较好的前端体验指标量化解决方案。

总的来说,包括3个方面:

第一就是:

幻灯片8.PNG

基于Grafana的数据收集和可视化

既然开发环境的实验室数据,样本量太小,不具有代表性,那我们干脆就从生产环境收集海量的真实用户数据,来量化前端项目的用户体验。

要实现这个目标,我们就需要一套收集,储存和可视化数据的解决方案。

经过对比内外部的各种工具,我发现开发体验最好的工具,非Grafana莫属。

Grafana 是一款开源的数据可视化工具,主要有以下特性:

  1. 兼容 ElasticSearch, Graphite, Prometheus 等各种数据源的数据查询工具;

  2. 置海量可视化图表模板的前端应用;

  3. GitHub开源,支持免费的私有化部署;

右边这张截图,就是使用Grafana制作出的一张数据可视化图表,做这样一张图表很方便,熟练以后,只需要几分钟,开发体验很好。

有了收集数据的工具,传统的前端优化理念会建议采集load事件触发耗时、白屏时间、首次有意义渲染 First Meaningful Paint 等传统的性能指标数据。

但是传统的指标往往有各种缺点。

幻灯片9.PNG

例如FMP首次有意义渲染,难以准确定义有意义这个概念,不便于稳定量化、准确测量。

或者无法准确代表,例如load事件是页面中JS、CSS等各类资源都加载完成后才触发的。对于spa应用,前端路由的跳转,加载耗时,load无法衡量,总之 load事件也不足以代表用户对网页体验。

并且就像我们之前介绍的,性能优化不等于体验优化,过去那些传统指标并不足以代表用户的体验。

因为体验是主观的,同样的网页在不同的软硬件条件下,对不同的用户会带来不同的感受。

前端应用在生产环境中面对的正是千差万别的海量用户,所以多年以来前端社区在量化用户体验方面尝试过许多实践。

近两年来,经过实践检验、之有效一套量化指标,是由谷歌总结了海量用户数据后,发明的5类web-vitals指标。

幻灯片10.PNG

1. 首次内容绘制 (First Contentful Paint,FCP)

FCP测量从页面开始加载到页面中任意部分内容,例如文本、图像、, 等,内容完成渲染的时长。 其值为浮点数类型,单位是毫秒。FCP 指标可以实现,在页面初始化阶段,把用户的主观体验量化为客观的数据。

2.最大内容绘制 (Largest Contentful Paint,LCP)

LCP测量从页面开始加载到可视区域内尺寸最大的文字或图像渲染完成的耗时。

其值为浮点数,单位是毫秒。

3.交互到绘制延迟(Interaction to Next Paint,INP)

INP测量用户在页面浏览过程中的所有交互与浏览器渲染的整体延迟情况。这一指标有1个细节需要关注,INP对页面全生命周期内的所有交互,都会测量。所以每一次交互都会记录一条INP指标数据。

第四类指标,针对的是网页中很让人厌烦,又很常见的一类问题,相信各位也一定见过,请看视频:

幻灯片11.PNG

这段视频中用户本想点击取消按钮,但是页面元素的布局位置突然产生了变化,顶部多出了一个横幅元素,出现了非用户交互导致的意外布局变化。

让原本取消按钮的位置被购买按钮替代了,用户本想点击取消,最终却意外地触发了点击购买的误操作,对用户造成了困扰,显著地损害了用户体验。

针对这类问题,发明了:

4.累积布局偏移 (Cumulative Layout Shift,CLS)

CLS测量页面中所有意外布局变化的累计分值。 其值为浮点数,无单位, 值的大小表示意外布局变化的多少和影响范围的大小。

CLS值的计算比较特殊,会统计一段时间内的所有意外布局变化,按特定算法,计算出CLS的分值。

5.第一字节时间 (Time to First Byte,TTFB)

TTFB测量前端页面(Document本身)的HTTP请求从发送后,到接收第一个字节数据响应的耗时,这段耗时通常包括重定向、DNS查询、服务器响应延迟等耗时。 页面HTTP响应的耗时越短,也就是页面的加载更快。

获取指标在前端应用中获取这些指标也非常简单,只需要使用谷歌开源的web-vitals库,调用其中的onFCP、onLCP等API即可获取上述几项指标的值。

并且这个库也帮我们处理了标签页是否处于后台隐藏状态,各浏览器之间API的差异等细节,可以确保不同的前端应用、在不同的浏览器中获取到统一标准的用户体验指标。

让我们看一条web-vitals库onFCP的返回的数据示例:

幻灯片13.PNG

从web-vitals获得的比较有统计意义的数据主要有:

  1. 各指标的数值(value),例如累计FCP的 "value": 63.20000076293945,;

  2. 评分(ratings):按官方标准对值进行划分得到的字符串值,共有优
    ('good'),待提升('needs-improvement'),差('poor')三类值,可用于对数据进行标准化处理;

  3. 指标的统计来源(sources):记录了计算各指标的来源,例如累计布局变化

CLS的sources字段记录的就是每次意外布局变化对应的DOM元素,及其变化前后的位置尺寸数据;

通过以上介绍,相信各位可以感受到web-vitals这一套指标是相当科学合理的。

都是对直接影响用户主观体验感受的因素进行测量,最终实现把用户的主观体验量化为客观数据。

而且基于谷歌的海量数据,对各个指标的值做了等级划分,便于开发者判断指标状况的优劣登记。

还提供了标准的开源库,供开发者方便地获取统一标准的指标数据。

下面我想分享一些使用这套指标的一些实践经验。

一般开发者都会选择观察统计value字段的值,统计其平均值、最大最小值,来对优化效果进行量化。

我前些年做用户体验量化用的也是value,但是经过几年的实践经验,我发现 value有一个显著的缺点

幻灯片14.PNG

就是会常常遇到指标值异常波动的问题,即在前端项目没有任何变更的前提下,却观察到指标的平均值产生了10%以上的显著波动变化。

例如这幅LCP值平均值可视化图中,04-22 号前后,也就是图中的左边这个低谷,这里的 LCP 平均值在我们的前端项目没有变更的情况下,就出现了减少

11%的变化,这样的异常波动显然不利于评估我们优化的效果。

波动的原因一般是因为在生产环境中,因为用户环境千差万别,web-vitals获取的值(value) 的波动范围较大,个别极端值,会导致统计出的平均值显著波动。

那么如何解决这个问题呢?

我探索出的经验是使用评分字段(ratings) 作为指标观测的目标,这样做相当于对观测的指标做了一次标准化处理,将一定范围内的值处理成统一的评分,有助于规避个别极端值导致的异常波动。

因此,我更推荐大家使用web-vitals的rating评分字段,配合百分比堆叠图来统计和可视化上述5类用户体验指标。

幻灯片15.PNG

百分比堆叠图就是,以日期为X轴,百分比0-100%为Y轴,把web-vitals的ratings字段值为数据源,可视化展示指标中good、needs-improvement、 poor 也就是优、待提升、差3类值各自占比多少。

通过观察3类值的占比,可以非常直观地看出用户体验指标的现状及其变化情况。

例如此处的示例图:红色部分就是评分为差poor的用户比例,黄色是needsimprovement待提升,绿色是good优。

并且因为ratings字段相当于做了一次标准化处理,避免了最大最小值这类极端值导致的平均值波动,所以上述数据异常波动问题也就迎刃而解。

经过我的实践检验,百分比堆叠图统计的各评分的占比,长时间内都能保持不超过5%的波动。有助于我们准确地量化评估前端体验优化的效果。

有了量化用户体验的手段,我们就可以正式开始前端体验优化了。

下面我就介绍一类投入产出比非常高的前端体验优化方案:

2. 资源优先级提示

首先,我们从一个常见的具体问题开始分析。

幻灯片16.PNG

如图,这是前端应用中的一次JS文件加载,在Devtool中的耗时记录。

可以看到,总计1.4秒的加载耗时中,分成了6个阶段,其中耗时较长的是3个:

1.  初始链接和SSL:955毫秒

2.  正在等待服务器响应:395毫秒

3.  内容下载:41毫秒

耗时占比最大的阶段是初始链接和SSL:955毫秒,如果能跳过这一阶段,对于

JS资源的加载耗时,以及相应的用户体验,将会有显著的优化效果。

那么,接下来一节介绍的内容就可以让我们实现跳过《初始链接和SSL》阶段的优化目标,这一方案就是:资源优先级提示--用2行代码让静态资源的加载耗时减少67%。

幻灯片17.PNG

先看示例图:

幻灯片18.PNG

这张图的上下2部分,分别是优化前后,同一JS文件在Devtool中的加载耗时。优化前加载总耗时是1400ms,优化后就骤降到了451ms,降幅达到了67%。相应的影响用户体验的页面加载耗时也会有显著改善,也能从我们之前介绍的

FCP指标可视化图上体现出来。

并且,如此显著的优化效果,只需要2行代码就可以实现,投入产出比非常高。

这类优化方案就是《资源优先级提示》:资源优先级提示是浏览器平台为控制资源加载时机而设计的一系列API,主要包括:

1.预取回 Prefetch

2.预加载 Preload

3.预连接 Preconnect

4.DNS预取回 DNS-Prefetch

共4类,下面我们来一一详细了解其功能和细节。

幻灯片19.PNG

1.预取回 Prefetch

首先是,预取回提示用于提示浏览器在CPU和网络带宽空闲时,预先下载指定 URL的JS,图片等各类资源,存储到浏览器本地缓存中,从而减少该资源文件后续加载的耗时,从而优化用户体验。

具体使用方式是将link标签的rel属性设为prefetch,并将href属性设为目标资源URL。

该标签插入DOM后,将触发一次href属性值对应URL的HTTP请求,并将响应保存到本地的prefetch cache中,同时不会进一步解析、运行该资源,例如对

一个JS进行prefetch,JS内的代码逻辑并不会执行,这是Prefetch的一大特点。

可以预取回的资源有很多:JS、CSS、各种格式的图片、音频、WASM文件、字体文件、甚至HTML文档本身都可实施 prefetch,预先缓存。命中预取回缓存的请求,在开发者工具中的Network标签中的Size列,会有独特的(prefetch cache)标记。同时可以看到因为是本地缓存,加载的耗时也非常短,只有1ms。

幻灯片20.PNG

2.预加载 Preload

第二类是 预加载 preload,与预取回不同,预加载用于提高当前页面中资源加载的优先级,确保关键资源优先加载完成。

预加载最常见的用法是用于字体文件优先加载,减少因字体加载较慢导致的文字字体闪烁变化。应用了preload提示的资源,通常会以较高的优先级率先在网页中加载,例如图中的nato-sans.woff2请求,Priority列的值为High,加载顺序仅次于Document本身,能让字体较早在页面中渲染生效,也有助于优化用户体验。

幻灯片22.PNG

3.预连接 Preconnect

第三类,预连接提示用于提示浏览器提前与目标域名进行HTTP握手,完成DNS 寻址,并建立TCP和TLS链接。

具体使用方式是将link标签的rel属性设为preconnect,并将href属性设为目标域名。

预连接的优化效果是通过提前完成DNS寻址、建立TCP链接和完成TLS握手,从而减少后续对目标域名的连接耗时,改善用户体验。

注意! 强烈建议只对重要域名进行Preconnect优化,数量不要超过 6 个。

因为Preconnect生效后,会与目标域名的保持至少10秒钟的网络连接,会占用设备的网络和内存资源,甚至阻碍其他资源的加载。过多的预连接会对用户的网络状况造成负面影响。

4.DNS预取回 DNS-Prefetch

第四类是DNS预取回,与上文的预取回Prefetch不同,DNS预取回用于对目标

域名提前进行DNS寻址,取回并缓存域名对应的IP地址,而非像预取回

Prefetch那样对文件资源缓存。

具体使用方式是将link标签的rel属性设为dns-prefetch,并将href属性值设为目标域名。

dns-prefetch实际上是preconnect的子集,他的功能被包含在了preconnect中。但是dns-prefetch的浏览器兼容性更广泛,兼容到 IE10+和Safari5+。而preconnect则完全不被IE浏览器兼容,Safari的兼容性也要到2018年后发布的11以上版本。

所以实践中dns-prefetch和preconnect,还是会一并使用,确保有更广泛的浏览器兼容性。

例如,我们网站的有部分懒加载的静态资源,部署在域名为static.juniortour.com的CDN上,那么在HTML中添加如下2行包含dnsprefetchPreconnect的HTML代码:

幻灯片24.PNG

就能观察到网页中懒加载资源触发加载时,对目标域名的JS、CSS等资源加载耗时会显著减少。因为预连接 Preconnect 的生效使得资源加载时的DNS寻址、SSL握手等阶段得以提前进行,各资源加载时的总耗时就大幅减少,会产生显著的优化效果。

这一节开头的优化效果示例就是应用了预连接和DNS预取回这2项资源优先级提示,具体的优化收益数据可以参考这份数据:

幻灯片25.PNG

这一节的最后,再为大家介绍一个工具库:resource-hint-generator,可以方便快捷地自动生成资源优先级提示。

幻灯片26.PNG

这个工具库产生的背景是,在开发实践中,我们的前端项目经常要发版上线,前端静态资源URL中的Hash版本会随之更新,相应的资源优先级提示的目标

URL也需要同步更新,如果手动做这件事,效率就太低了,开发体验也很痛苦。

所以,我就开发这个工具库,实现在每次打包构建完成后,扫描产物,根据我们指定的配置,自动生成一份JS脚本,包含新的资源优先级提示清单,加载这个JS,就会自动在我们前端应用的HTML中注入资源优先级提示的HTML标签,应用资源优先级提示优化。

接下来的第三节是:

幻灯片27.PNG

3. 代码分割最佳实践:细粒度代码分割

代码分割是前端优化中最常用的手段之一,几乎所有的前端优化文章都会介绍这一优化方案,但是内容往往比较陈旧,大都是把前端代码分割成vendors、 common2类,这类传统的解决方案,经过实践检验有很多痛点和问题。

具体来说,体现在以下方面:

幻灯片28.PNG

  1. 配置复杂,开发体验不佳:

Webpack的SplitChunk 插件有十几项核心的配置,chunks、minSize、maxInitialRequests 等等,各类繁杂的配置项令开发者困惑,难以确定拆分目标模块

  1. 配置方案健壮性不强,可维护性不好:拆分配置方案无法适应项目的快速迭代变化,需要经常调整;

  2. 最根本的问题:优化用户体验的效果不好:传统的代码分割拆分效果不好,拆分出的模块每次打包上线都会变化,不便于配合增量构建进行缓存,没有实现最优缓存效果,甚至导致用户体验恶化;

所以今天我们不讲这些陈词滥调,而是分享一下如何解决这些痛点?讲一讲代码分割的最佳实践:细粒度代码分割方案

幻灯片29.PNG

细粒度代码分割(Granular Code Split)

细粒度代码分割(Granular Code Split) 是近年来发明的代码分割通用解决方案,其经过 Next.js, Gastby 等前端框架多年的实践验证,能有效解决上述传统方案的痛点,同时显著改善开发体验和用户体验。

这一方案的核心思路是通过配置,自动拆分出数十个体积小、粒度细的打包产物文件。

同时,利用HTTP/2协议多路复用的特性,避免因加载静态资源数量过多导致的网络阻塞问题。

目标是实现前端项目在多次打包部署上线后,仍然能复用之前版本的文件缓存,不必重新下载,从而加快页面加载,优化用户体验。

// webpack.production.config.js
const crypto = require('crypto');

const MAX_REQUEST_NUM = 20;
// 指定一个 module 可以被拆分为独立 区块(chunk) 的最小源码体积(单位:byte)
const MIN_LIB_CHUNK_SIZE = 10 * 1000;

const isModuleCSS = (module) => {
  return (/*...*/)
};

module.exports = {
  mode: 'production',
  optimization: {
    splitChunks: {
      maxInitialRequests: MAX_REQUEST_NUM,
      maxAsyncRequests: MAX_REQUEST_NUM,
      minSize: MIN_LIB_CHUNK_SIZE,
      cacheGroups: {
        defaultVendors: false,
        default: false,
        lib: {
          chunks: 'all',
          test(module) {
            return (
              module.size() > MIN_LIB_CHUNK_SIZE &&
              /node_modules[/\\]/.test(module.identifier())
            );
          },
          name(module) {
            // ... 详细代码逻辑请参考下一节改造示例
            return 'lib_' + hash.digest('hex').substring(0, 8)
          },
          priority: 3,
          minChunks: 1,
          reuseExistingChunk: true,
        },
        shared: {
          chunks: 'all',
          name(module, chunks) {
            return `shared.${crypto
              .createHash('sha1')
              .update(
                chunks.reduce((acc, chunk) => {
                  return acc + chunk.name;
                }, ''),
              )
              .digest('hex')
              .substring(0, 8)}${isModuleCSS(module) ? '.CSS' : ''}`;
          },
          priority: 1,
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
    },
  },
  // ...
};

如截图所示,细粒度代码分割配置指定了2类区块:

  1. lib:配置核心是test指定的匹配规则,表示指定lib缓存组只包含来自 node_modules目录,且源码体积10KB的模块。

lib缓存组用于把体积较大的NPM包模块,拆分为独立区块,产生独立产物文件,从而在多次打包部署后、避免哈希版本号文件名更新,让用户加载页面时可以之间从缓存中加载静态资源,不必再次下载lib类型的模块,从而提高缓存命中率,减少资源下载体积,加快页面加载,改善用户体验。

  1. shared: 配置的核心是minChunks: 2,用于指定shared缓存组只包含被2个及以上区块共用的模块代码。

一个具体的例子就是知乎PC端的首页,大家可以检查看看这个网站加载的JS文件数量。会发现数量足有50个以上,非常多。

幻灯片30.PNG

按照传统代码分割和前端优化的思路,这么多静态资源同时加载似乎是个缺陷?似乎对页面加载和用户体验有负面影响。

但加载大量静态资源文件,这一点正是细粒度代码分割的特点和优点,也并不会产生负面影响。

因为知乎用的CDN是基于HTTP/2协议的,有多路复用的特性,从一个域名加载1个资源和加载50个资源,加载速度差距不大,不会造成网络堵塞,加载缓慢。

同时,因为代码分割的粒度更细,产物文件更多。当前端应用多次打包部署后,大部分的资源文件仍然能复用之前旧版本的缓存,进而加快页面加载速度,优化用户体验。

细粒度代码分割有显著的优点:

• 开发体验好:配置统一通用,可以实现自动选择拆分目标模块,不必人工判断哪些模块需要拆分,降低了代码分割的使用门槛;

• 健壮性强:以不变应万变,用这套不变的代码分割配置可以应对不断更新迭代的各类型前端项目,不必经常更新配置,便于维护;

• 用户体验好:分割颗粒度较细,产物文件稳定,多次打包部署后,仍有较多文件名称内容不变,静态资源的缓存命中率高,缓存效果好,有利于改善用户体验。

幻灯片31.PNG

如何渲染页面决定了前端应用的用户体验,要想从根本上优化前端页面的加载表现和用户体验,尤其是首次内容绘制(FCP)和 最大内容绘制 (Largest Contentful Paint,LCP)2项用户体验指标,改变前端应用的渲染方式无疑是最强大的杀手锏。

所以第4节,我想介绍一些SSR相关的内容,主题是

幻灯片32.PNG

4. SSR架构进阶优化:CSR 到 SSG、ISR

相信在座的各位都有切身体会,前端应用首次内容绘制耗时长、白屏问题显著、交互卡顿等很多用户体验痛点,都和目前流行的渲染方式:客户端渲染 Client Side Render 息息相关。

客户端渲染的前端应用有天然的痛点:

幻灯片33.PNG

• 首先,就是我们刚才提到的,它的用户体验有明显短板:因为浏览器对JS的下载和解析执行需要一定耗时,导致客户端渲染的前端应用都有初始化时页面白屏的问题。

• 再有,CSR的搜索引擎优化(SEO)也较差:因为搜索引擎爬虫程序一般不支持执行JS,但CSR又必须执行JS后才能渲染出页面内容,所以无法适应爬虫的索引内容需求,导致CSR的前端应用在搜索引擎中排名靠后,流量较少。

• 最后,CSR前端应用的开发体验也有一些缺点:前后端分离也带来了一些不便之处,例如:CSR应用最核心的入口页面index.html不得不维护在后端项目中,不便于前端工程师修改调试,造成了前端能力的缺陷。

所以,近些年致力于解决上述CSR痛点的服务端渲染(Server Side Render,SSR)前端应用大行其道。目前流行的SSR架构核心原理大都基于前端框架的2项能力实现,分别是:

幻灯片34.PNG

1.Node.js服务端渲染为HTML字符串renderToString(element: ReactElement)

2.浏览器客户端中的活化hydrate(element: ReactElement, container: HTMLElement)

如果你的前端应用是CSR架构,苦于加载慢、白屏长、FCP指标差等体验问题,与其使出浑身解数,锱铢必较地优化方方面面的细节,不如调整渲染方式,使用SSR架构,从根本上,根治CSR前端应用的用户体验痛点。

下面我想介绍2类SSR的进阶优化方案: SSR架构的应用中或多或少会有一些内容长期不变的纯静态页面,例如博客文章页、活动页和规则条例页等等。

幻灯片35.PNG

服务端静态站点生成 (SSG, ic Site Generation )

对于这些千人一面的静态页面,每次用户的请求时都消耗服务端资源进行 SSR 渲染,生成相同的HTML字符串,显然是一种对计算资源的浪费,因此就有了服务端静态站点生成 (Static Site Generation SSG)这项优化技术。

SSG允许开发者指定部分页面,在项目打包编译时,就提前生成静态页面HTML 文件,随服务端应用部署后,直接将HTML文件作为响应返回给用户。

从而避免执行服务端渲染,减少运行开销,节省服务器硬件资源,减少金钱开销。

但是,SSG也有其痛点:

•首先,SSG只适用于完全静态的页面,如果页面中一小部分需要在SSR时动态变化,SSG就难以实现。

•其次,对于有大量静态页面的SSR应用项目,大量的页面需要在打包构建时渲染生成,会导致项目的构建耗时相当漫长,让开发体验变差。

为了进一步解决SSG的这些痛点,

增量静态页面再生(ISR,IncrementalStatic Regeneration)

技术应运而生。

ISR通过将应用运行时,单次SSR调用renderToString()渲染的结果缓存下来,并设置上缓存有效期,来实现既减少重复渲染,节省服务端资源,又避免构建耗时太长的优化目标。

我在过去的工作中,也曾为内部一个有千万日活的SSR前端应用,实践过ISR优化。

幻灯片36.PNG

具体做法是,通过借鉴 Next.js 的ISR实现方案,为我们内部SSR应用的服务端渲染逻辑,加上了一层LRU Cache,并且支持前端路由级别的缓存有效期和动态缓存Key配置。

优化上线之后的优化效果非常显著,这3张图表就是我当时监控的几项量化指标,可以看到:

1.服务端渲染耗时的平均值,从优化前的3ms减少到了0.02ms,减少了99.2%;

2.HTTP响应的整体耗时,从优化前的31.3ms减少到了26.5ms,减少了15%;

3.服务端应用容器实例的内存用量,从优化前的198MB减少到了175MB,减少了11%。

这次的ISR优化,还有一些细节经验想分享:

1.ISR的灵活性确实很高,可以自定义缓存使用的键名key,实现一个前端路由对应N份缓存,很适合有静态页面的SSR前端应用。

2.并且,将来还可以进一步使用redis保存缓存,利用其高性能、分布式锁的特性,实现更好的优化效果,强化前端团队的服务端运维能力。

好的,以上就是我今天分享的全部内容,感谢各位的聆听。关于前端体验优化,我还有很多想和大家聊的。如果各位对这一主题感兴趣,可以看看我写的一本小册《前端体验优化实战》,也发布在掘金平台上。

我今天来分享,最大的心愿就是能多交些朋友,由I人转E人,以后当一个E人,欢迎各位助力我实现这个心愿,来找我面对面聊天,或者加我微信交个朋友。

幻灯片37.PNG

[《前端工程体验优化实战ttps:https://juejin.cn/book/7306163555449962533)全书现已发布到掘金小册,欢迎各位朋友交流学习

简介:不止性能优化,6000行源码+14类前端优化方案,打造前端体验优化理念,超越传统前端性能优化。

小册海报

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant