React 服務器端渲染和客戶端渲染效果對比

React 服務器端渲染和客戶端渲染對比

最近在學習 React 的服務端渲染,於是使用 Express+React 寫了一個 Demo,用於對比和客戶端渲染的差異。github 地址

先看一下效果吧:

1、訪問 服務器端渲染 Online Demo

2、我們可以看到,首屏數據很快的就顯示出來了,可是頁面的進度條卻還在加載中(因爲客戶端 js 很大)。

3、當進度條加載完成後,頁面才能進行交互操作(切換路由,登錄等)。

4、查看網頁源代碼,頁面內容都在頁面中。

效果不明顯的話,可以打開控制檯,在 Network 欄 Disable cache,然後刷新。

通過這次簡單的訪問,我們就能看出服務器端渲染的 2 大特點,首屏直出SEO 友好

爲什麼要做服務器端渲染?

1、訪問 客戶端渲染 Online Demo

2、我們可以看到,首屏至少等待了 6 秒才渲染出來,這對於一般的用戶,是難以容忍的。

3、不過一旦渲染完成,頁面就立即可交互了(切換路由,登錄等)。

4、查看網頁源代碼,頁面只有一個空 div 容器,而沒有實際內容。

通過這次訪問,我們就能看出客戶端渲染的特點,首屏加載時間長SEO 不友好,但可見即可操作

其實我們在訪問客戶端渲染的頁面時,請求到的只是一個 html 空殼,裏面引入了一個 js 文件,所有的內容都是通過 js 進行插入的,類似於這樣:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>ssr</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="bundle.js"></script>
  </body>
</html>

正是因爲頁面是由 js 渲染出來的,所以會帶來如下幾個問題:

1、頁面要等待 js 加載,並執行完成了才能展示,在這期間頁面展現的是白屏

2、爬蟲不能識別 js 內容,所以抓取不到任何數據,不利於 SEO 優化

爲了解決這 2 個問題,我們可以使用服務器端渲染。

React 服務器端渲染流程

之前說道,客戶端渲染的頁面,請求到的是一個 html 空殼,然後通過 js 去渲染頁面。那如果請求到的直接是一個渲染好的頁面,是不是就可以解決這 2 個問題了呢?

沒錯,服務器端渲染就是這個原理。

簡化流程

1、服務器端使用 renderToString 直接渲染出包含頁面信息的靜態 html

2、客戶端根據渲染出的靜態 html 進行二次渲染,做一些綁定事件等操作。

服務器端沒有 DOM,Window 等概念,所以只能渲染出字符串,不能進行事件綁定,樣式渲染等。

只有第一次訪問頁面時才使用服務器端渲染,之後會被客戶端渲染接管。

開始寫代碼吧

接下來我們一起來寫一個 React 服務器端渲染 Demo。

編寫路由

這裏使用 react-router 對前後端代碼進行同構。

1、客戶端

使用 react-router-dom 下的 BrowserRouter 進行前端路由控制。

2、服務器端

使用 react-router-dom 下的 StaticRouter 進行靜態路由控制,具體操作如下:

  • 使用 react-router-config 下的 matchRoutes 匹配後端路由,使用 renderRoutes 渲染匹配到的路由。
  • 使用 react-router-dom/server 下的 renderToString 方法,渲染出 html 字符串,並返回給前端。
使用 StaticRouter 中通過 context 可以和前端頁面通信,傳參。

狀態管理

在 React 中,我們常常使用 redux 來存儲數據,管理狀態。

1、客戶端

使用 redux 進行狀態管理,使用 react-redux 提供的 Provider 爲組件注入 store。

2、服務器端

和客戶端一樣,但每一次接收到請求需產生一個新的 store,避免多個用戶操作同一個 store。

數據請求

1、客戶端

使用 axios 在 componentDidMount 中請求數據。

2、服務器端

同樣使用 axios 去請求數據,但是服務器端不會觸發 componentDidMount 生命週期。我們可以在後端匹配到路由的時候,進行數據請求,並把數據存入 redux 中的 store,然後渲染出包含數據的 html 頁面,爲了避免客戶端二次請求,服務器端向 window 中注入 REDUX_STORE 數據,客戶端直接使用此數據作爲客戶端 redux 的初始數據,以免發生數據抖動。

具體操作如下:

  • 在 routes 對象上掛載一個自定義方法 loadData。
  • 在服務器端 matchRoutes 後,如果有 loadData,則進行請求數據,並把請求到的數據寫入 store 中。
  • 服務器端等待請求完成後,再進行 renderToString 渲染。

樣式處理

1、客戶端

使用 css-loader,style-loader 打包編寫好的 css 代碼並插入到頁面中。

2、服務器端

由於 style-loader 會插入到頁面,而服務器端並沒有 document 等概念,所以這裏使用 isomorphic-style-loader 打包 css 代碼。

  • 引入 isomorphic-style-loader 後,客戶端就可以通過 styles._getCss 方法獲取到 css 代碼。
  • 通過 staticRouter 中的 context 把 css 代碼傳入到後端。
  • 後端拼接好 css 代碼,然後插入到 html 中,最後返回給前端。

SEO 優化

SEO 主要是針對搜索引擎進行優化,爲了提高網站在搜索引擎中的自然排名,但搜索引擎只能爬取落地頁內容(查看源代碼時能夠看到的內容),而不能爬取 js 內容,我們可以在服務器端做優化。

常規的 SEO 主要是優化:文字鏈接多媒體

  • 內部鏈接儘量保持相關性
  • 外部鏈接儘可能多
  • 多媒體儘量豐富

由於網頁上的文字,鏈接,圖片等信息都是產品設計好的,技術層面不能實現優化。我們需要做的就是優化頁面的 title,description 等,讓爬蟲爬到頁面後能夠展示的更加友好。

這裏藉助於 react-helmet 庫,在服務期端進行 title,meta 等信息注入。

你可能不需要服務器端渲染?

現在,我們成功地通過服務器端渲染解決了首次加載白屏時間SEO 優化。但也帶來了一些問題:

  • 服務器端壓力增大。
  • 引入了 node 中間層,可維護性增大。

以上兩個問題歸根結底還是錢的問題。服務器壓力大,可以通過買更多的服務器來解決。可維護性增大,可以招募更多人來維護。但是對於小型團隊來說,增加服務器,招募更多維護人員,都會額外增加的支出,所以在選擇服務器端渲染時,要權衡好利弊。

解決 SEO 的另一種方法

如果只是想優化 SEO,不妨使用預渲染來實現,推薦使用 prerender 庫來實現。

prerender 庫的原理:先請求客戶端渲染的頁面,把客戶端渲染完成之後的結果,拿給爬蟲看,這樣爬蟲獲取到的頁面就是已經渲染好的頁面。prerender 庫在使用時會開啓一個服務,通過傳遞 url 來解析客戶端渲染頁面,這就需要我們對服務器端架構進行調整。

1、 nginx 判斷訪問類型

2.1、 用戶訪問 :直接走客戶端渲染

2.2、 爬蟲訪問 :走預渲染

總結

通過這個 Demo,讓我加深了對服務器端的理解,如有錯誤,麻煩多多指正,謝謝大家!

如果覺得有用得話給個 ⭐ 吧。react-ssr-demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章