基于 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. SpringCloud(四) config

    Spring Cloud Config 在分布式系统中,尤其是当我们的分布式项目越来越多,每个项目都有自己的配置文件,对配置文件的统一管理就成了一种需要,而 Spring Cloud Config 就 ...

  2. 【LeetCode】363. Max Sum of Rectangle No Larger Than K 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/max-sum- ...

  3. 第七个知识点:随机性如何辅助计算和什么是BPP类问题

    第七个知识点:随机性如何辅助计算和什么是BPP类问题 原文地址:http://bristolcrypto.blogspot.com/2014/11/52-things-number-7-how-doe ...

  4. 如何基于LSM-tree架构实现一写多读

    一  前言 PolarDB是阿里巴巴自研的新一代云原生关系型数据库,在存储计算分离架构下,利用了软硬件结合的优势,为用户提供具备极致弹性.海量存储.高性能.低成本的数据库服务.X-Engine是阿里巴 ...

  5. 深度探索 OpenStack Neutron:BGP(1) 【转载】

    3.4 BGP 原文地址:http://mp.weixin.qq.com/s?src=3&timestamp=1500043305&ver=1&signature=XwiIVV ...

  6. CS5210完全替代AG6202|HDMI转VGA不带音频输出的芯片+原理图|替代兼容AG6202

    CS5210完全替代AG6202|HDMI转VGA不带音频输出的芯片+原理图|替代兼容AG6202 安格AG6202是一个HDMI转VGA不带音频解决方案,用于实现HDMI1.4高分辨率视频转VGA转 ...

  7. Android开发布局 案例一

    权重:就是在布局界面中所占的比例 实践案例: <?xml version="1.0" encoding="utf-8"?> <LinearLa ...

  8. Kafka基础教程(二):Kafka安装

    因为kafka是基于Zookeeper的,而Zookeeper一般都是一个分布式的集群,尽管kafka有自带Zookeeper,但是一般不使用自带的,都是使用外部安装的,所以首先我们需要安装Zooke ...

  9. Fuchsia OS入门官方文档

    Fuchsia Pink + Purple == Fuchsia (a new Operating System) Welcome to Fuchsia! This document has ever ...

  10. sqlserver - 判断字段是否是纯数字

    PATINDEX('%[^0-9|.|-|+]%',w.waterMeterNo)=0 如 SELECT w.* FROM [dbo].[waterMeterInfo] w where isnull( ...