CSR、SSR、Prerender 原理全解密

作前端的同窗們確定或多或少據說過CSRSSRPrerender這些名詞,可是大多確定只是停留在據說過,瞭解過,略懂一點,可是,你真的理解這些技術嗎?html

這些名詞具體是什麼意思呢?前端

爲何會產生這種技術,要解決的問題是什麼呢?node

每種技術背後的原理又是什麼呢?react

從各自的概念和執行流程提及

在瞭解這些概念以前,咱們要先了解一個熟知的概念,那就是 SPA(Single Page Application),沒錯,就是你們熟知的單頁應用,其實 CSR、SSR、Prerender 都是基於 SPA,關於 SPA 的概念我就很少闡述了。webpack

CSR(Client Side Render)(客戶端渲染)

即,渲染過程所有交給瀏覽器進行處理,服務器不參與任何渲染。web

打包下來頁面是這個樣子:shell

  
  
  
  

流程:瀏覽器 --> 服務器 --> index.html(白屏) --> bundle.js --> images --> Renderredux

image-20191231113956671

咱們來使用 create-react-app 來創建一個 web 工程,並在 Chrome 裏使用 slow 3G 網絡下作個實驗:瀏覽器

Xnip2019-12-29_17-49-12

能夠看到,從頁面空白到元素繪製用了足足 12 秒左右,這個白屏時間太可怕了,這也就是爲何在 Web App 盛行的當下,包體積愈來愈大會致使白屏時間愈來愈長,你們想要優化這個現象的緣由。緩存

Prerender(Pre Render)(預渲染)

即,打包的時候就預先渲染頁面,因此在請求到 index.html 就已是渲染過的內容

流程:瀏覽器 --> 服務器 --> index.html(預渲染的內容) --> Render --> bundle.js + images --> Render

image-20191231113759430

咱們將剛剛的工程加入 prerender-spa-plugin 這個插件,再次運行看看結果

此次打包下來的主頁 html 是這個樣子的:

  
  
  
  

首頁就已經預渲染好了,這時咱們再來運行一次看看:

Xnip2019-12-30_13-54-09

此時能夠看到,頁面只用了 2 秒就已經渲染出元素,不會形成長時間的白屏問題

SSR(Server Side Render)(服務器渲染)

流程:瀏覽器 --> 服務器 --> 服務器執行渲染 --> index.html(實時渲染的內容)) --> Render --> bundle.js + images --> Render

image-20191231111204363

可見 SSR 在服務端多作了一些實時渲染的操做,那麼咱們此次運行下來回事什麼結果呢?

Xnip2019-12-31_11-17-55

能夠看出來,SSRPrerender 的效果一致,都能很好的減小白屏時間

總結

從上面的實驗,能夠看出來,不管是 SSR 仍是 Prerender,咱們要解決的問題主要是白屏時間太長的問題,這兩種技術都是爲了解決 CSR 的不足之處,那麼這兩種方案有什麼區別?使用場景又有哪些呢?

P.S 其實另外一方面的緣由是,CSRSEO 太不友好了,搜索引擎抓取不到關鍵信息,只能抓取一個毫無元素的白屏頁面,會致使搜索引擎搜索不到你的頁面信息進行推薦,SSRPrerender 都能很好的解決這個問題。(吐槽一下:Google 已經實現了抓取基於 SPACSR

Prerender or SSR

在作出選擇以前,咱們必需要充分的瞭解二者的差別。

Prerender 更加通用可是侷限性太大

  1. Prerender 原理是在構建階段就將 html 頁面渲染完畢,不會進行二次渲染,也就是說,當初打包時頁面是怎麼樣,那麼預渲染就是什麼樣,若是頁面上有數據實時更新,那麼瀏覽器第一次加載的時候只會渲染當時的數據,等到 JS 下載完畢再次渲染的時候纔會更新數據更新,會形成數據延遲的錯覺。

  2. Prerender 須要預先指定須要渲染的頁面,須要手動在 webpack 裏設置

        
        
        
        

    因此頁面數量很大的狀況下,想將每一個頁面進行預渲染是很大工做量,並且打包時間會很長,還可能會遺漏

說了那麼多利弊,那麼,預渲染是怎麼作到生成頁面的呢?

作過爬蟲的同窗確定知道 headless 的概念

Headless Chrome 在 Chrome59 中發佈,用於在 headless 環境中運行 Chrome 瀏覽器,也就是在非 Chrome 環境中運行 Chrome。它將 Chromium 和 Blink 渲染引擎提供的全部現代 Web 平臺功能引入命令行。 它有什麼用處呢? headless 瀏覽器是自動測試和服務器環境的絕佳工具,您不須要可見的 UI shell。例如,針對真實的網頁進行測試,建立網頁的 PDF,或者只是檢查瀏覽器如何呈現 URL。

Prerender 就是利用 Chrome 官方出品的 Puppeteer 工具,對頁面進行爬取。它提供了一系列的 API, 能夠在無 UI 的狀況下調用 Chrome 的功能, 適用於爬蟲、自動化處理等各類場景。它很強大,因此很簡單就能將運行時的 HTML 打包到文件中。

原理是在 Webpack 構建階段的最後,在本地啓動一個 Puppeteer 的服務,訪問配置了預渲染的路由,而後將 Puppeteer 中渲染的頁面輸出到 HTML 文件中,並創建路由對應的目錄。

下面是流程圖

image-20191231143313360

SSR 雖好可是要框架支持

目前來講,主流的框架 ReactVue 都已經支持 SSR,只是配置會繁瑣點,有人就會疑惑,框架還要支持 SSR

可事實是,正是由於現代 SPAVirtual DOM 的存在,才能使 SSR 變成現實,可是,SSR 這種理念的實現,並不是易事。

咱們先來看看詳細的 SSR 流程圖:

image-20191231172059262

能夠看出,SSRPrerender 的最大區別就在於,Prerender 是靜態的,SSR 是動態的,SSR 會在服務端實時構建出對應的 DOM

這也是 SSR 的難點所在:同構(即服務器和瀏覽器共同構建)。

何爲同構

同構這個概念存在於 VueReact 這些新型的前端框架中,同構其實是客戶端渲染和服務器端渲染的一個整合。咱們把頁面的展現內容和交互寫在一塊兒,讓代碼執行兩次。在服務器端執行一次,用於實現服務器端渲染,在客戶端再執行一次,用於接管頁面交互。

上面咱們說過,SSR 的工程中,React 代碼會在客戶端和服務器端各執行一次。你可能會想,這沒什麼問題,都是 JavaScript 代碼,既能夠在瀏覽器上運行,又能夠在 Node 環境下運行。但事實並不是如此,若是你的 React 代碼裏,存在直接操做 DOM 的代碼,那麼就沒法實現 SSR 這種技術了,由於在 Node 環境下,是沒有 DOM 這個概念存在的,因此這些代碼在 Node 環境下是會報錯的。

可是就是因爲 Virtual DOM 技術的存在,讓這一切變成了可能,這裏不過多介紹 Virtual DOM,簡單來講,它就是一個普通的 JS 對象,只不過映射了 HTML DOM 的結構,React 在作頁面操做時,實際上不是直接操做 DOM,而是操做 Virtual DOM,也就是操做普通的 JavaScript 對象,這就使得 SSR 成爲了可能。

咱們能夠直接在代碼裏判斷當前的運行環境,若是是瀏覽器,就能夠直接操做 DOM ,若是是服務器,就須要使用 Virtual DOM 生成 HTML 字符串。

到了這裏彷彿一切都很簡單,一切都這麼順其天然,可是問題又出現了,路由怎麼辦

瀏覽器路由和服務器路由徹底是兩種不一樣的運行機制,SPA 瀏覽器路由機制能夠看這裏,其實緣由很簡單,在服務器端須要經過請求路徑,找到路由組件,而在客戶端需經過瀏覽器中的網址,找到路由組件,是徹底不一樣的兩套機制,因此這部分代碼是確定沒法公用。

因此 React 分別爲瀏覽器端和服務器端分別提供了 BrowserRouterStaticRouter 兩種路由,經過 BrowserRouter 咱們可以匹配到瀏覽器即將顯示的路由組件,對瀏覽器來講,咱們須要把組件轉化成 DOM,因此須要咱們使用 ReactDom.render 方法來進行 DOM 的掛載。而 StaticRouter 可以在服務器端匹配到將要顯示的組件,對服務器端來講,咱們要把組件轉化成字符串,這時咱們只須要調用 ReactDom 提供的 renderToString 方法,就能夠獲得 App 組件對應的 HTML 字符串。

那麼,如今差很少要完成了吧

尚未!

對於一個 React 應用來講,路由通常是整個程序的執行入口。在 SSR 中,服務器端的路由和客戶端的路由不同,也就意味着服務器端的入口代碼和客戶端的入口代碼是不一樣的。而入口則是 Webpack 進行打包完成的。

針對代碼運行環境的不一樣,要進行有區別的 Webpack 打包,咱們須要在 Webpack 的配置中加入 target: 'node',代表是服務器環境進行打包,除此以外,還有各類各樣的配置須要解決。

等等,萬一要用到 Redux 來進行狀態管理呢

若是要用到 redux 進行全局狀態管理,必定要記得寫成這種形式:

  
  
  
  

由於服務器端的 Store 是全部用戶都要用的,可是不能讓全部用戶共享 Store ,因此在服務器端渲染中,Store 的建立應該像下面這樣,返回一個函數,每一個用戶訪問的時候,這個函數從新執行,爲每一個用戶提供一個獨立的 Store

最後還有什麼注意點嗎

因爲服務器不存在掛載元素這一輩子命週期,因此例如 ReactcomponentDidMount 或者 VUEmounted 生命週期都不會執行了,因此在服務端利用接口獲取數據的時候,不能寫入上述的生命週期中。

最後總結

  1. 若是頁面無數據,或者是純靜態頁面,建議使用 Prerender,這是一種經過預覽打包的方式構建頁面,也不會增長服務器負擔,但其餘狀況並不推薦。
  2. 當訪問量過大時,SSR 的實時構建會加重服務器 CPU 的消耗,需結合其餘技術進行處理(例如 CDN,服務器緩存,負載均衡等)
  3. 若是頁面數據請求多,又對 SEO 和加載速度有需求的,建議使用 SSR
  4. 對於高操做需求的項目來講,CSR 可能更加適合,頁面顯示元素即綁定了操做,而 SSRPrerender 雖然會提早顯示頁面,但此時頁面元素沒法操做,仍須要下載完 bundle.js 進行事件綁定才能執行

固然在真正實現 SSR 架構的過程當中,難點有時不是實現的思路,而是細節的處理。好比說如何針對不一樣頁面設置不一樣的 titledescription 來提高 SEO 效果,這時候,咱們其實能夠用 react-helmet 這樣的工具幫咱們達成目標,這個工具對客戶端和服務器端渲染的效果都很棒,值得推薦。還有一些諸如工程目錄的設計,404,301 重定向狀況的處理等等,不過這些問題,咱們只須要在實踐中遇到的時候逐個攻破就能夠了。

相關文章
相關標籤/搜索