基于 SSR 的預(yù)渲染首屏直出方案

基于 SSR 的預(yù)渲染首屏直出方案

Create React Doc 是一個(gè)使用 React 的 markdown 文檔站點(diǎn)生成工具。此前在 Create React Doc 中引入了預(yù)渲染技術(shù)來(lái)預(yù)先生成對(duì)應(yīng)路由的靜態(tài)頁(yè)面,以使基于其搭建的文檔站點(diǎn)能享用到 SEO(Search Engine Optimization) 同時(shí)加快了首屏訪問(wèn)加載。

新的挑戰(zhàn)

Create React Doc 使用預(yù)渲染技術(shù)獲取各頁(yè)面路由對(duì)應(yīng)的 DOM 結(jié)構(gòu)以生成對(duì)應(yīng)的 HTML 文件,并將靜態(tài)文件存放于 gh-pages 服務(wù)中(可自行選擇其它存儲(chǔ)服務(wù))從而達(dá)到加快首屏訪問(wèn)加載以及 SEO。見(jiàn)如下藍(lán)色線框流程圖部分:

image

下圖為 gp-pages 服務(wù)存放的靜態(tài)目錄文件:

image

在訪問(wèn) Create React Doc 創(chuàng)建的文檔時(shí),頁(yè)面渲染周期可分為首屏渲染階段、銜接階段、可交互階段。

首屏渲染階段: 以訪問(wèn)快速上手章節(jié)為例,當(dāng)用戶在瀏覽器輸入 http://muyunyun.cn/create-react-doc/290a4219/ 時(shí),gp-pages 服務(wù)會(huì)推送預(yù)先渲染好的頁(yè)面,此時(shí)用戶可以獲得十分快速的首屏體驗(yàn) ??。

image

不過(guò)需要指出的是,預(yù)渲染的頁(yè)面僅僅只是生成靜態(tài)的 HTML 頁(yè)面,因而在首屏渲染階段的頁(yè)面時(shí)用戶是無(wú)法交互的。

銜接階段: 銜接階段是首屏渲染階段頁(yè)面可交互階段的中間態(tài)階段,在該階段執(zhí)行 JavaScript 邏輯,從而使頁(yè)面從不可交互到可交互。但是觀察發(fā)現(xiàn)從預(yù)渲染頁(yè)面到頁(yè)面可交互,出現(xiàn)了干擾體驗(yàn)的加載頁(yè),體驗(yàn)十分不好 ??。

image

不被期望的中間加載頁(yè)(見(jiàn)上圖)出現(xiàn)的原因?yàn)轭A(yù)渲染頁(yè)面與客戶端渲染頁(yè)面都使用了 ReactDom.render 并指定相同根路徑節(jié)點(diǎn)(這里為 root)進(jìn)行渲染。在訪問(wèn)首屏預(yù)渲染頁(yè)面之后,執(zhí)行 JavaScript 邏輯時(shí),React 會(huì)移除存量 HTML 結(jié)構(gòu),并基于 root 節(jié)點(diǎn)重新開(kāi)始渲染,因而必然會(huì)導(dǎo)致出現(xiàn)不被期望的加載頁(yè)或者頁(yè)面抖動(dòng)。

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

可交互階段:該階段用戶可以與頁(yè)面進(jìn)行交互。比如點(diǎn)擊左側(cè)菜單按鈕可以展開(kāi)、收起等。

image

基于 SSR 的預(yù)渲染首屏直出方案

基于文檔站點(diǎn)大部分為靜態(tài)內(nèi)容,少部分為動(dòng)態(tài)可交互內(nèi)容。抽象出以下幾種可行性思路:

  • 思路一:調(diào)整交互布局,減少動(dòng)態(tài)節(jié)點(diǎn)的交互。比如使用面包屑組件與平鋪菜單結(jié)構(gòu)來(lái)替換多層級(jí)菜單,或者探尋更優(yōu)雅的 CSS 交互方案。

  • 思路二:解耦靜態(tài)節(jié)點(diǎn)與動(dòng)態(tài)交互節(jié)點(diǎn)渲染的時(shí)機(jī)。預(yù)渲染時(shí)完成大部分靜態(tài)頁(yè)面的渲染,在銜接階段中完成動(dòng)態(tài)邏輯節(jié)點(diǎn)的執(zhí)行。偽代碼如下:

if (!ifProdRender) {
  // 預(yù)渲染靜態(tài)節(jié)點(diǎn)
  ReactDOM.render(
    <QuietNode />,
    document.getElementById('quietNode'),
  )
} else {
  // 銜接階段完成動(dòng)態(tài)交互節(jié)點(diǎn)的渲染
  ReactDOM.render(
    <DynamicNode />,
    document.getElementById('dynamicNode'),
  )
}

基于上述代碼,可實(shí)現(xiàn)靜態(tài)頁(yè)面節(jié)點(diǎn)與動(dòng)態(tài)交互節(jié)點(diǎn)的分開(kāi)渲染。但該方案的缺陷是靜態(tài)節(jié)點(diǎn)與動(dòng)態(tài)交互節(jié)點(diǎn)之間的聯(lián)系被完全割裂開(kāi),銜接階段渲染的節(jié)點(diǎn)不能影響到靜態(tài)頁(yè)面節(jié)點(diǎn),比如頁(yè)面布局、路由跳轉(zhuǎn)等。

  • 思路三:解耦靜態(tài)節(jié)點(diǎn)渲染與動(dòng)態(tài)交互生效的時(shí)機(jī),保證靜態(tài)節(jié)點(diǎn)與動(dòng)態(tài)交互節(jié)點(diǎn)渲染之間的聯(lián)系。在思路二基礎(chǔ)上,進(jìn)一步聯(lián)想到如果基于服務(wù)端渲染(在服務(wù)端首屏直出靜態(tài)頁(yè)面,在客戶端注水交互邏輯)不就可以完美支持靜態(tài)節(jié)點(diǎn)與動(dòng)態(tài)交互隔離執(zhí)行,同時(shí)保證銜接階段頁(yè)面不出現(xiàn)抖動(dòng)了么。只不過(guò)我們這里的服務(wù)端可以使用 gh-pages 服務(wù)來(lái)存放基于 SSR 提前預(yù)渲染好的節(jié)點(diǎn)。
image

根據(jù)環(huán)境執(zhí)行不同的渲染邏輯的代碼如下示意,完整改動(dòng)可見(jiàn) 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'),
  )
}

至此在銜接階段中不友好的抖動(dòng)問(wèn)題(不被期望的加載頁(yè))得以解決,用戶在訪問(wèn)站點(diǎn)時(shí)不會(huì)再感受到由于頁(yè)面抖動(dòng)帶來(lái)不友好的體驗(yàn),同時(shí)從首屏渲染頁(yè)到頁(yè)面可交互的銜接也變得更為順滑。

小結(jié)

在靜態(tài)內(nèi)容為主的文檔站點(diǎn)中,除了首屏加載速度、SEO 之外,從首屏頁(yè)面(不可交互)到可交互階段的中間銜接態(tài)的體驗(yàn)也十分重要?;?React 技術(shù)生態(tài)前提下,本文給出了基于 SSR 的預(yù)渲染首屏直出的解法以相對(duì)完美地解決了銜接態(tài)出現(xiàn)的頁(yè)面抖動(dòng)問(wèn)題。在即將到來(lái)的 React 18 中,我們可以讓節(jié)點(diǎn)的交互更為即時(shí)地被響應(yīng),以更進(jìn)一步優(yōu)化用戶訪問(wèn)體驗(yàn),讓我們拭目以待吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容