基于 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. SpringBoot项目存放微信的验证文件,把微信的验证文件放在根目录下

    我们做微信开发的时候,有时候会有如下要求 用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠. 下载文件 将以下文件上传至填写域名或路径指向的w ...

  2. c++设计模式概述之组合(composite)

    代码写的不够规范,目的是为了缩短代码篇幅, 实际中请不要这样做 1.概述 这里的组合,是将 部分组合到整体.所以, 用到的对象有: 部分.整体. 这里的例子,生活中可以类比厨房的筷筒: 里面放了筷子, ...

  3. 惊!世界上竟然有O(N)时间复杂度的排序算法!计数排序!

    啥?你以为排序算法的时间复杂度最快也只能O(N*log(N))了? O(N)时间复杂度的排序算法听说过没有?计数排序!!它是世界上最快最简单的算法!!! 计数排序算法操作起来只有三步,看完秒懂! 根据 ...

  4. 【剑指Offer】包含min函数的栈 解题报告

    [剑指Offer]包含min函数的栈 解题报告 标签(空格分隔): 牛客网 题目地址:https://www.nowcoder.com/questionTerminal/beb5aa231adc45b ...

  5. 难搞的偏向锁终于被 Java 移除了

    背景 在 JDK1.5 之前,面对 Java 并发问题, synchronized 是一招鲜的解决方案: 普通同步方法,锁上当前实例对象 静态同步方法,锁上当前类 Class 对象 同步块,锁上括号里 ...

  6. 【C++】leetcode竞赛笔记

    *注--代码非博主本人所写,仅供学习参考,侵删 20200516双周赛: 1 to_string(int a) 将a转换成字符串形式 2 gcd(int a,int  b) 返回a,b的最大公约数,若 ...

  7. Towards Deep Learning Models Resistant to Adversarial Attacks

    目录 概 主要内容 Note Madry A, Makelov A, Schmidt L, et al. Towards Deep Learning Models Resistant to Adver ...

  8. 欢迎收看 Flink Forward Asia 2021 峰会直播

    在线收看地址:https://developer.aliyun.com/special/ffa2021/live#?utm_content=g_1000316459 Flink 是由 Apache 软 ...

  9. linux 之 命令提示符设置

    linux 命令提示符设置实际就是设置环境变量PS1的值. 参数说明 \d 显示时间,星期 月 日 \h 显示简写主机名,如默认主机名 localhost \t 显示24小时制时间, HH:MM:SS ...

  10. python3 f-string格式化字符串的高级用法

    从Python 3.6开始,f-string是格式化字符串的一种很好的新方法.与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在Python 3.6之前,有两种将Python表 ...