无代码系统服务端渲染实践
背景
公司目前有两套无代码系统,其中有一套就是我们团队负责,由于无代码系统的灵活性,不仅经常用于活动页面,也经常用作首页,无代码的技术方案,但是由于无代码页面的动态性,白屏时间相较普通SPA或者经过SSR渲染的页面白屏时间更长,因此不得不根据无代码的系统的特性定制一套服务端渲染方案。
由于历史的原因我们当前的无代码系统技术栈包含部分自己开发的MVC框架和微前端架构,其中View采用了基于浏览器解析DOM的模版引擎(这在后面会成为服务端渲染的坑)。当然也支持React作为View使用。
什么是服务端渲染?
服务端渲染(Server-Side Rendering),是指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。
简单来说就是服务端根据当前路由返回html,首屏浏览器根据HTML生成DOM并渲染页面,JS复用首屏的DOM,在DOM上绑定事件,最终完成服务端渲染。
与SSR类似的首屏优化解决方案还有SSG
下面是Next.js对二者的定义
SSR
- Server-side Rendering: The HTML is generated on each request.
SSG
- Static Generation (Recommended): The HTML is generated at build time and will be reused on each request.
SSG是在编译阶段生成HTML,SSR 是在用户访问时生成HTML,但是这两者对于浏览器来说表现是一致的
对于无代码系统创建的页面来说,页面中呈现的内容并不是开发者部署代码的那一刻决定的,而是在无代码后台编辑并发布的那一刻决定的,所以根据路由在编译阶段生成HTML,SSG方案肯定是无法使用了。
// 配图比较传统SPA和无代码系统
如果对服务端渲染流程还不是很清晰的同学可以 通过 CRA项目支持服务端渲染 – 掘金 (juejin.cn) 感受一下服务端渲染需要做哪些工作,以及基本的思路。
方案调研
1、开源方法
Next.js umi.js morder.js 这些都提供了现成的服务端渲染解决方案,对于从0开发的项目来说,用这些框架能达到事半功倍的效果,但是对于已有项目来说,路由,构建等都有很大的差异,迁移成本极高。
2、基于renderToString搭建
这种是社区中出现比较频繁的一种方案,利用在Nodejs 环境下运行 React提供的 renderToString 方法渲染出Html,这也是Next 等开发框架SSR的底层逻辑,只是我们需要根据自身业务定制,由于浏览器环境和Nodejs 环境的差异,如果想要迁移现有项目,并且构建一个自己的服务端渲染来说,工作量仍然是巨大的。 这里有一个把让Cra项目支持服务端项目的文章,可以感受一下其复杂程度 CRA项目支持服务端渲染 – 掘金 (juejin.cn)
3、无头浏览器
这种方案与方案2最大的区别在于,HTML的生成环节有所不同,前者是在Nodejs 环境中运行渲染逻辑,而后者是在无头浏览器中渲染HTML并返回给用户,这种方案的优点在于,对老项目的兼容性很强,因为无论是在客户端还是服务器中的无头浏览器,环境是高度一致的,无需处处小心。当然这种方案的渲染效率一定没有方案二的效率高,硬件消耗同样也会更高。这就需要我们通过缓存,任务队列等方案做好资源管理。避免对硬件的过度消耗。
方案设计
- 为了避免每一次请求都进行渲染,我们需要有缓存机制,这里我们采用的是Redis,
- 优先使用缓存,如果缓存中没有,加入渲染队列,并非实时渲染只是加入队列
基于以上两个原则,我们请求过程的流程图如下:
访问流程
渲染器
上述流程中,对于请求的页面我们并没有实时渲染,所以我们需要一个渲染器,
在服务器空闲时,按照一定的优先级渲染页面。
渲染的优先级我们采用的是LFU算法,优先渲染请求次数最多的页面,
为了避免无头浏览器过度占用内存,浏览器新开页面数量也做了一定的限制,只有空闲时才会从队列中取出下一个需要渲染的页面进行渲染。
缓存管理
对于缓存的及时清理相当关键,如果页面更新了但是没有更新缓存会导致用户看到的第一帧是老的页面,清理缓存应该尽量遵循最小原则。
清理缓存的时机有
1、CSR应用发布(包含了无代码模块的逻辑),由于CSR应用的发布不确定改动的模块,目前会清理所有的redis缓存
2、无代码后台更新页面,只需请理解当前页面的缓存即可
3、修改模块信息,需要清理所有用到该模块的所有页面的缓存
结合上述步骤渲染和缓存的完整流程是
结语
以上就是我们无代码系统的整体服务端渲染方案,
对于技术栈相对复杂,前期并没有考虑的项目来说无头浏览器为我们提供了一种可能性。
文章有些地方可能没有具体阐述,比如如何确保最小化更新,实际开发中缓存更新的策略可能会更加复杂有一些。但是总体的思路希望能够帮到大家。