原文地址: https://medium.freecodecamp.o...
Server-Side Rendering :SSR 是一種前端框架可以在後端渲染出HTML的能力。那些可以在客戶端和服務端完成渲染的應用就叫作universal apphtml
爲了理解爲何須要SSR,這裏咱們須要瞭解下web應用在過去十年內的發展史。SSR與SPA(Single Page Application)的興起緊密相連。與傳統的服務端渲染的app相比,SPA在速度和用戶體驗發麪存在很大的優點。
可是使用SPA有個問題,一般狀況下用戶第一次請求會返回一個空html文件和一堆JS和CSS連接,渲染html以前會先把JS和CSS提早下載下來。
這就意味着首次渲染的時候,用戶必需要等更長的時間。同時對於爬蟲來講,解析到的頁面也是一個空頁面。
所以SSR的主要思想就是首次在server端渲染應用,後續能夠充分利用SPA的優點,在客戶端完成渲染。
SSR + SPA = Universal App
有的文章中也會把Universal App講成isomorphoic app,實際上這兩個是同一個東西。採用SSR的狀況下,首次渲染的時候,用戶不須要等JS加載完成後在看到渲染完成的頁面,而是在請求返回的時候就已經拿到渲染完成的頁面了。前端
對於使用slow 3G的用戶來講,使用SSR會大大改善用戶體驗。用戶將會直接看到網頁內容而不是等待20s+才能看到網頁內容。
如今狀況下,全部發送到server端的請求都會被直接返回成HTML。這樣對於作SEO的部門來講也是十分有利的。
對爬蟲來講不會區別對待SPA引用和其餘靜態站點,一樣會爲服務端渲染的內容生成索引。
簡而言之,使用SSR有兩點好處:react
下面筆者將經過一個例子,一步步來實現一個完整的SSR案例。首先從React的服務端渲染API開始,後續每一步咱們都將加入一些新的內容。
能夠follow 這個項目倉庫 ,每一步的代碼都會有一個tag,讀者能夠經過git checkout tags/xxx -b xxx
的方式獲取每一步的代碼(xxx爲對應的tag名)。git
開始介紹SSR以前,咱們須要一個server。這裏筆者採用express來渲染React應用。github
在代碼的第10行,咱們用express啓動了一個靜態服務器。同時咱們也建立了一個用於處理非靜態請求的handle函數。非靜態的路由將會返回HTML代碼。web
在代碼的第13~14行,咱們用renderToString
函數把一開始的JSX代碼轉換成字符串,這段字符串後續將被插入到HTML模板中。express
PS:咱們並無直接啓動sever.js ,而是經過index.js來啓動server.js。在index.js中,咱們用babel插件來抹平client和server端的差別,保證client和server都可以使用es module和jsx。redux
在SSR中client端的代碼也須要從ReactDOM.render
的改爲ReactDOM.hydrate
。這個函數將會接受服務端渲染的react代碼並掛載事件處理函數。後端
想看到完整的例子,能夠check react-ssr tag爲basic的代碼。到這兒爲止,咱們就完成了一個簡單的服務端渲染的react app。promise
到目前爲止,咱們的應用實際上啥事也沒幹。如今咱們來往以前的應用加入一些路由。先來看看如何處理服務端部分:
如今Layout
組件在client端上將會渲染出路由組件。對應的咱們須要在server端模擬出client的路由實現。下面咱們列出server
端代碼的修改部分:
在服務端的代碼中,咱們須要把React Application包裝在StaticRouter
組件中,並提供location
參數。
PS:context
用於在渲染React DOM
的過程當中追蹤可能的重定向請求:好比client須要根據3XX響應重定向。
完整的案例須要checkout tag爲router
的代碼。
在項目已經具有路由能力的狀況下,下面咱們來集成redux
。一些場景下,咱們須要使用redux來管理client端的狀態。可是在服務端渲染的狀況,如何根據當前狀態來渲染部分DOM是個問題,所以咱們有必要在服務端初始化redux。
若是應用在服務端dispatch action的狀況下,SSR須要記錄下這些操做,並把最終的state和HTML一塊兒返回給client。在client端,會把服務端返回的state設爲redux的初始狀態。
咱們先來看看server端的實現:
這段代碼看起來實現的十分醜陋,可是咱們確實須要把服務端渲染出來的redux狀態和HTML代碼一塊兒返回給client。
接着來看看client部分的實現:
這裏咱們調用了兩次createStore
,一次在server端,一次在client端。可是在client端上須要把server端保存下來的狀態設爲redux的初始狀態。
完整的例子能夠看當前項目的redux
tag。
最後一步就是加載數據。這是個比較棘手的問題。咱們從一個返回JSON數據的接口開始講起。
在代碼倉庫中,筆者經過開放API獲取了2018第一賽季的Formula的數據。咱們但願在Home頁面顯示全部的Formula數據。
咱們能夠在全部React app
掛載完成、全部元素都已經渲染完畢的的狀況下調用API接口來完成需求。若是這樣的話,可能會存在一些loading畫面,對於用戶體驗並不友好。
考慮到項目中已經整合了Redux
,咱們能夠經過Redux
來保存數據並返回給前端的方式來加載數據。
如何在server端調用API接口、將接口返回數據保存在Redux中並讓客戶端根據相關數據來渲染HTML呢?
那麼須要調用哪些接口呢?
首先咱們須要經過一種不一樣的方式來申明路由。因此咱們把路由改爲以下所示:
同時咱們也須要在組件上聲明全部的數據:
PS: fetchData
是一個 Redux thunk action
,dispatch fetchData的時候會返回一個promise
。
同時在服務端,咱們也用了一個react-router
中的特殊的函數:matchRoute
:
經過這個方法,當服務端根據當前URL渲染頁面的時候會獲得須要被mounted
的組件。咱們會收集全部組件須要的數據,等待全部接口都已經返回數據,並把獲取的數據塞到redux中才會繼續執行服務渲染。
切換到tag爲fetch-data
的分支能夠看到整個案例。
從這兒開始,咱們就會開始從各個維度進行比較,並比較出哪些場景適合使用SSR哪些場景不適合使用SSR。好比說對一個電商app來講,獲取全部的產品是重中之重,可是價格以及一些其餘的邊欄filter相比之下就顯得不那麼重要。
最後咱們來看看SEO。當和React打交道的時候,咱們常常須要在<head>
標籤中設置不一樣的值。好比:title
、meta tags
、keywords
等等。
記住<head>
標籤中的內容通常不是React App
的一部分。
react-helmet就是爲了解決修改<head>
標籤中的內容而生的,並對SSR提供了良好的支持。
你能夠在組件樹中的任何地方加入head標籤內的數據。在client端上,react-helmet提供了一種修改React App
之外部分的能力。
咱們也在SSR中加入這種能力:
如今咱們已經顯示了一個具有基礎功能的React服務端渲染的案例。咱們從一個返回HTML內容的express應用開始,慢慢加入了路由、狀態管理以及獲取數據的能力。最後咱們還處理React App
以外的部分。完整的代碼在master分支能夠看到。
正如本文所示,SSR並不適合一件難事,可是SSR也能夠作的很複雜。若是一步一步來實現須要會更容易些。那麼項目中是否須要加入SSR呢?具體狀況具體分析。若是網站訪問量很大,則建議作SSR。可是若是你的應用是相似於工具或者dashboard這種應用,則不必花費較多的精力來實現SSR。