基于 SSR 的预渲染首屏直出方案

Create React Doc 是一个使用 React 的 markdown 文档站点生成工具。此前在 Create React Doc 中引入了预渲染技术来预先生成对应路由的静态页面,以使基于其搭建的文档站点能享用到 SEO(Search Engine Optimization) 同时加快了首屏访问加载。

新的挑战

Create React Doc 使用预渲染技术获取各页面路由对应的 DOM 结构以生成对应的 HTML 文件,并将静态文件存放于 gh-pages 服务中(可自行选择其它存储服务)从而达到加快首屏访问加载以及 SEO。见如下蓝色线框流程图部分:

下图为 gp-pages 服务存放的静态目录文件:

在访问 Create React Doc 创建的文档时,页面渲染周期可分为首屏渲染阶段衔接阶段可交互阶段

首屏渲染阶段: 以访问快速上手章节为例,当用户在浏览器输入 http://muyunyun.cn/create-react-doc/290a4219/ 时,gp-pages 服务会推送预先渲染好的页面,此时用户可以获得十分快速的首屏体验 。

不过需要指出的是,预渲染的页面仅仅只是生成静态的 HTML 页面,因而在首屏渲染阶段的页面时用户是无法交互的。

衔接阶段: 衔接阶段是首屏渲染阶段页面可交互阶段的中间态阶段,在该阶段执行 JavaScript 逻辑,从而使页面从不可交互到可交互。但是观察发现从预渲染页面到页面可交互,出现了干扰体验的加载页,体验十分不好 。

不被期望的中间加载页(见上图)出现的原因为预渲染页面与客户端渲染页面都使用了 ReactDom.render 并指定相同根路径节点(这里为 root)进行渲染。在访问首屏预渲染页面之后,执行 JavaScript 逻辑时,React 会移除存量 HTML 结构,并基于 root 节点重新开始渲染,因而必然会导致出现不被期望的加载页或者页面抖动。

ReactDOM.render(
<RouterRoot />,
document.getElementById('root'),
)

可交互阶段:该阶段用户可以与页面进行交互。比如点击左侧菜单按钮可以展开、收起等。

基于 SSR 的预渲染首屏直出方案

基于文档站点大部分为静态内容,少部分为动态可交互内容。抽象出以下几种可行性思路:

  • 思路一:调整交互布局,减少动态节点的交互。比如使用面包屑组件与平铺菜单结构来替换多层级菜单,或者探寻更优雅的 CSS 交互方案。

  • 思路二:解耦静态节点与动态交互节点渲染的时机。预渲染时完成大部分静态页面的渲染,在衔接阶段中完成动态逻辑节点的执行。伪代码如下:

if (!ifProdRender) {
// 预渲染静态节点
ReactDOM.render(
<QuietNode />,
document.getElementById('quietNode'),
)
} else {
// 衔接阶段完成动态交互节点的渲染
ReactDOM.render(
<DynamicNode />,
document.getElementById('dynamicNode'),
)
}

基于上述代码,可实现静态页面节点与动态交互节点的分开渲染。但该方案的缺陷是静态节点与动态交互节点之间的联系被完全割裂开,衔接阶段渲染的节点不能影响到静态页面节点,比如页面布局、路由跳转等。

  • 思路三:解耦静态节点渲染与动态交互生效的时机,保证静态节点与动态交互节点渲染之间的联系。在思路二基础上,进一步联想到如果基于服务端渲染(在服务端首屏直出静态页面,在客户端注水交互逻辑)不就可以完美支持静态节点与动态交互隔离执行,同时保证衔接阶段页面不出现抖动了么。只不过我们这里的服务端可以使用 gh-pages 服务来存放基于 SSR 提前预渲染好的节点。

根据环境执行不同的渲染逻辑的代码如下示意,完整改动可见 mr

if (ifDev) {
// dev render
document.getElementById('root').innerHTML = ReactDOMServer.renderToString(<RouterRoot />)
ReactDOM.hydrate(
<RouterRoot />,
document.getElementById('root'),
)
} else if (ifPrerender) {
// prerender
document.getElementById('root').innerHTML = ReactDOMServer.renderToString(<RouterRoot />)
} else {
// prod render
ReactDOM.hydrate(
<RouterRoot />,
document.getElementById('root'),
)
}

至此在衔接阶段中不友好的抖动问题(不被期望的加载页)得以解决,用户在访问站点时不会再感受到由于页面抖动带来不友好的体验,同时从首屏渲染页到页面可交互的衔接也变得更为顺滑。

小结

在静态内容为主的文档站点中,除了首屏加载速度、SEO 之外,从首屏页面(不可交互)到可交互阶段的中间衔接态的体验也十分重要。基于 React 技术生态前提下,本文给出了基于 SSR 的预渲染首屏直出的解法以相对完美地解决了衔接态出现的页面抖动问题。在即将到来的 React 18 中,我们可以让节点的交互更为即时地被响应,以更进一步优化用户访问体验,让我们拭目以待吧。

基于 SSR 的预渲染首屏直出方案的更多相关文章

  1. 移动 H5 首屏秒开优化方案探讨

    转载bang大神文章,原文<移动 H5 首屏秒开优化方案探讨>,此文仅仅用做自学与分享! 随着移动设备性能不断增强,web 页面的性能体验逐渐变得可以接受,又因为 web 开发模式的诸多好 ...

  2. 腾讯新闻构建高性能的 react 同构直出方案

    在腾讯新闻抢金达人活动 node 同构直出渲染方案的总结文章中我们整体了解了下同构直出渲染方案在我们项目中的使用.正如我在上篇文章结尾所说的: 应用型技术的难点不是在克服技术问题,而是在于能够不断的结 ...

  3. vue客户端渲染首屏优化之道

    提取第三方库,缓存,减少打包体积 1. dll动态链接库, 使用DllPlugin DllReferencePlugin,将第三方库提取出来另外打包出来,然后动态引入html.可以提高打包速度和缓存第 ...

  4. 腾讯优测优分享 | 探索react native首屏渲染最佳实践

    腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~ 此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app ...

  5. 探索react native首屏渲染最佳实践

    文 / 腾讯 龚麒 0.前言 react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react n ...

  6. 服务端预渲染之Nuxt(介绍篇)

    现在前端开发一般都是前后端分离,mvvm和mvc的开发框架,如Angular.React和Vue等,虽然写框架能够使我们快速的完成开发,但是由于前后台分离,给项目SEO带来很大的不便,搜索引擎在检索的 ...

  7. vue项目使用 prerender-spa-plugin 预渲染

    由于项目要做seo优化,而用vue写成的spa页面谷歌浏览器等是抓取不到数据的.介于ssr和预渲染来说,后者相对来说要简单许多.所以采用了预渲染方式.采用插件prerender-spa-plugin使 ...

  8. Vue项目预渲染机制引入实践

    周末想顺便把已经做好静态页面的webApp项目做一下SEO优化,由于不想写蹩脚的SSR代码,所以准备采用预渲染,本来想着网上有这么多预渲染的文章,随便找个来跟着做不就完了嘛,结果年轻的我付出了整个周末 ...

  9. Vue项目预渲染机制

    我们知道SPA有很多优点,不过一个缺点就是对(不是Google的)愚蠢的搜索引擎的SEO不友好,为了照顾这些引擎,目前主要有两个方案:服务端渲染(Server Side Rendering).预渲染( ...

随机推荐

  1. 再谈多线程模型之生产者消费者(单一生产者和单一消费者)(c++11实现)

    0.关于 为缩短篇幅,本系列记录如下: 再谈多线程模型之生产者消费者(基础概念)(c++11实现) 再谈多线程模型之生产者消费者(单一生产者和单一消费者)(c++11实现)[本文] 再谈多线程模型之生 ...

  2. 【LeetCode】1051. Height Checker 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 排序比较 日期 题目地址:https://leetc ...

  3. 【LeetCode】747. Largest Number At Least Twice of Others 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 寻找两次最大值 排序 大顶堆 日期 题目地址:htt ...

  4. 【LeetCode】74. Search a 2D Matrix 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 左下或者右上开始查找 顺序查找 库函数 日期 题目地 ...

  5. centos 各版本下载

    地址: go to http://vault.centos.org/ for packages.

  6. datatables scrollX设置水平滚动无效问题

    如下:设置了水平滚动之后, 页面并没有滚动效果$(document).ready(function() { $('#example').dataTable( { "scrollX" ...

  7. 如何在 Go 中将 []byte 转换为 io.Reader?

    原文链接: 如何在 Go 中将 []byte 转换为 io.Reader? 在 stackoverflow 上看到一个问题,题主进行了一个网络请求,接口返回的是 []byte.如果想要将其转换成 io ...

  8. Azure Data Lake(一) 在NET Core 控制台中操作 Data Lake Storage

    一,引言 Azure Data Lake Storage Gen2 是一组专用于大数据分析的功能,基于 Azure Blob Storage 构建的.Data Lake Storage Gen2 包含 ...

  9. 揭秘人脸对齐之3D变换-Java版(文末赋开源地址)

    一.人脸对齐基本概念 人脸对齐通过人脸关键点检测得到人脸的关键点坐标,然后根据人脸的关键点坐标调整人脸的角度,使人脸对齐,由于输入图像的尺寸是大小不一的,人脸区域大小也不相同,角度不一样,所以要通过坐 ...

  10. Postman保存token并使用token的整个流程

    1. 调通获取token的接口,包含传递参数的类型,和输入密码是否需要md5加密,根据接口的要求,传入数据 2. 查看接口响应的报文,可以看到token的有效时间,token的类型,里面的access ...