业务背景
截至到当前2022年,在工作中前端业务主要呈现以下特点
- 1.前后端解耦,明确分工,提高了效率,缓和服务端压力
- 2.隔绝了DOM操作,专注于逻辑,解放了生产力
- 3.高效,用户体验好、响应速度快,web应用更令人着迷
- 4.跨平台,迭代快,研发成本低
传统的SPA项目的缺陷
- 1.传统无法满足SEO需求,尤其是对于电商业务,很大一部分流量来自于搜索引擎* 2.首屏加载慢,用户体验差,自身框架代码Crocodile加上React以及其他的库带来的较大的脚本下载以及启动时长下可能存在较为明显白屏…名词解惑-SPA
SPA:单页Web应用(single page application) 它将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript、CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转,而是利用JavaScript动态的变换HTML(采用的是div切换显示和隐藏),从而实现UI与用户的交互。在SPA应用中,应用加载之后就不会再有整页刷新。相反,展示逻辑预先加载,并有赖于内容Region(区域)中的视图切换来展示内容。
特点:前端路由,动态更新,响应快,体验好,但是无法被搜索引擎收录关键内容(SEO)且有较长的脚本下载与启动时间
名词解惑-SEO
SEO:搜索引擎优化(Search Engine Optimization) 它是一种通过分析搜索引擎的排名规律,了解各种搜索引擎怎样进行搜索、怎样抓取互联网页面、怎样确定特定关键词的搜索结果排名的技术,以此提高排名,提高网站访问与流量通过合理的文档内容,TKD、meta、H1等标签,canonical,og(开放内容协议,Open Granph Protocol->title,type,url)的处理可以优化
名词解惑-Hybrid/App
HybridApp:原生与web混合开发的APP,跨平台,实质最终发布的仍然是独立的原生APP用web站点(spa)内嵌在APP不同平台的WebView中,通过JSbridge作为通信桥梁,与APP约定行为调用原生功能,以达到满足web应用的迭代灵活开发高效的特点,跨平台但是又接近原生的体验,对应WebApp、NativeApp和HybridApp之一
名词解惑-首屏渲染
页面=模板+数据上面的加号(+)并不是加法,它就是我们今天要讲到的渲染(Render)
客户端渲染CSR (Client Side Rendering)(单一端处理便于维护,跨终端,但不利于SEO,首屏差)
服务端渲染SSR (Side Rendering) (SEO 友好、首屏渲染快,但复杂,服务端逻辑和耗时大)渲染 (Render)->首屏渲染时间:
首屏渲染时间是指浏览器从响应用户输入网址地址,到页面内容绘制完成可以交互的时间,即首次有效绘制(FMP),此时整个网页不一定要全部渲染完成,但在当前视窗的内容需要
首屏渲染优化:资源数目文件大小(压缩与打包),缓存(前后端,Cache-Control,ServiceWorker,本地存储机制),HTTP(协议版本,数量,速度,CDN分发),动态加载(路由,组件,图等资源),视觉过渡(loading,骨架),SSR ,脚本解析顺序,渲染阻塞,异步脚本等
现状
解析url ->返回没有主要内容的index.html
->(爬虫抓取完成)拉取样式、种子文件、框架脚本、库/插件
->加载业务系统主文件
->启动APP
->注册路由
->初始化各页面
->处理路由
->进入页面生命周期,执行页面逻辑
->页面静态内容渲染(白屏阶段结束,但是受制于接口返回和渲染速度)
->异步数据陆续返回,页面各部分渲染。
问题
业务上是面向C端的电商用户,产品信息需要支持搜索引擎抓取,技术上由Node和Webpack构建,SPA应用,Hybrid模式Web与APP一体,自研框架(React为基础),文件量大,动态数据,部分业务系统首屏耗时长,有明显的白屏 需要SEO支持和优化首屏性能!解法->SSR(服务端直接生成html内容返回给浏览器渲染首屏内容)
原理解析
SSR方案
1.用服务端语言重写一套页面给搜索引擎用(高成本)
2.理解JavaScript 解析器在服务端来解析客户端的脚本语言,例如服务端嵌入V8 解析器(低性能)
3.SSR框架,如next.js(没必要)理想方案是利用React对于SSR的支持做基于Node的JavaSript同构,“Write once,run everywhere”
ReactSSR核心实现
react-dom/server库
renderToString()带额外属性(常用)
renderToStaticMarkup()不带额外属性
renderToNodeStream()可读流renderToStaticNodeStream()静态页
基本方案
1.客户端服务端的脚本写法我们都遵照Node 的CommonJS 规则,在涉及同构的模块,尽量减少复杂依赖2.路由规则支持Express,放一个模块中前后端同时调用3.Web环境下,当URL解析后,服务端先处理路由规则,调用自身的页面展示组件,生成HTML 再呈现给用户,客户端JavaScript 加载完后,移除SSR渲染出的模块,切换为CSR模式4.APP环境通过Webpack打包成webapp时用plugin方式方式渲染为和SSR同样的静态直出入口文件
同构
服务端与客户端
同构Model(网络请求与数据处理层)2.组件样式3.路由规则和工具类(路由实现:H5 端基于浏览器popstate 事件(HistoryAPI),Hybrid 端基于浏览器hashchang 事件(hash)。同一套路由在启动时根据判断环境自动切换,与服务端实现对相同的路由解析规则保证这部分代码同构)
APP与H5
同构用于seo的View。2.同构缓存依赖处理以及其他逻辑流程
一、路由解析
baseRes对象中是各页面的TKD以及一些其他页面配置变量,配合index.ejs(第五张幻灯片的配图)使用输出用于SEO2.在此处同构路由中可以配置是直接返回基本视图还是需要用SEO处理句柄返回SEO处理过后的视图
二、整合
1.IndexContainer是用于服务端渲染的React组件,seoModel是用于服务端数据请求的模块
2.使用try catch,在seo处理异常时降级为普通基础视图做一个容灾
3.注意,引用和使用React组件需要提前引入对JSX语法的服务端支持,如bable的preset
三、视图组件
1.这就是一个普通的纯React组件容器,用于渲染服务端视图,除了不绑定事件,其他的与CSR组件保持一致
2.设定一个“csr-view”的类名,用于在csr端视图开始渲染的时候,移除ssr视图(一个疑问:最开始为什么类名不是ssr-view?)
3.视图将被Web端SSR和APP打包作为服务端渲染以后静态直出的同构模板使用
四、Model核心
这里展示就是SSR数据处理层的核心
1.”整合”模块(本张之前第二张)的execute做的事就是发送一个的数据请求的queue,在队列完成回调中,把整合模块作为参数传过来的回调带着数据触发。
2.AbstractIndexModel作为一个网络请求的构造器存储模块,在此处继承服务端接口请求模块的基类并分别被实例化,放入队列
3.构造器将被客户端和服务端的网络请求模块同构引用,但是不同之处在于客户端的接口请求模块基于XHR( XMLHttpRequest ),服务端是HTTP
五、同构的请求构造器
1.API地址与接口出具处理等模块将参与同构
2.在客户端,也只需要和服务端一样,引用构造器,继承基类实例化触发请求
六、Hybrid模式APP的处理
1.将如上SSR流程封装在打包插件中,在webpack构建的对应业务系统配置文件注册启动插件
2.拿到数据把图片下载下来,作为静态图放置于webapp包中
3.插件执行完用fs模块将SSR后的字符串写为静态html入口文件,将来APP打开的页面看到的便是一个SSR处理后的页面
根据我们的测试无论Android还是iOS首次初始化WebView时所花费差不多要300400ms,第二次初始化需要100200ms左右。可以看出第二次初始化要快一些,所以这里我们可以通过提前初始化一个WebView来提升性能,或共用一个Webview。
实际效果
采用SSR之后
解析url ->返回有内容的index.html
->拉取样式、种子文件、框架脚本、库/插件
->加载业务系统主文件
->启动APP->注册路由
->初始化各页面
->处理路由
->(不再白屏)进入页面生命周期,执行页面逻辑
->页面静态内容渲染
->异步数据陆续返回,页面各部分渲染
我们可以看到在初次加载中,除了极短的重新请求时间的闪动以外都很好,很重要的一点,我们的index.html审查原代码是看得到动态的产品数据的,说明支持了产品SEO
更好的方案
确实解决了大部分问题,但是确实还存在一些不足:1.APP的静态数据更新问题2.在没有本地缓存的时候闪动问题
方案:
1.在Web环境,将SEO数据作为静态JSON注入到index.html,APP环境,用webpack把静态数据打包成为依赖
2.在页面初始化的时候,区分环境收集依赖,如果有本地缓存,读取用于数据初始化,没有则用来自SEO或者打包的数据存入缓存,再数据初始化
3.当接口返回数据,更新对应缓存依赖
精益求精
补充的优化细节:
1.SSR的时候,评估首屏范围内的接口,在接口端的接口数据实例处理钩子中过滤不需要展示的数据和多余的字段
2.在APP端下载图片的时候,只下载显示的图,降低包的大小3.APP环境接口返回数据更新时,做数据比对,把匹配产品下载了的图替换网络地址4.其他根据场景而定的优化…
一些可以改进的点:
1.因为针对无网络环境做了PWA,有独立的缓存判别,所以第一次加载为了保险起见,SEO数据只是过渡数据,浪费了在Web环境的一轮请求
2.SPA页面逻辑过重,同构时SEO生成的view是直接移除覆盖(两次渲染)而不是复用绑定事件
3.针对SEO和APP的处理加大了服务端和打包过程中的业务代码量以及服务器负担,并且开发过程中的调试并不容易
4.“不完全”的同构,本次方案也只从视觉和SEO上进行了首屏优化
没有最好的技术,只有最合适的方案