React SSR 教學與問題整理 前端伺服器渲染觀念與常見問題

me
林彥成
2017-08-31 | 3 min.
文章目錄
  1. 1. 簡介
  2. 2. 實作小關鍵
  3. 3. Server Side Rendering Issues
  4. 4. 解決方案
  5. 5. Next.js

淺談 React 的前端專案如何實作 SSR,伺服器渲染有哪些好處,又會遇到哪些問題?

簡介

隨著瀏覽器的效能開始越來越好,帶有顯示邏輯的渲染可以轉移到瀏覽器端,於是也就慢慢出現了 Single Page Application 的概念,和以往散落在好幾頁的做法不同,現在將所有網頁程式都 bundle 成一個檔案,可以把 bundle 想像成是 APP 的 apk 檔,畫面中的元素不再透過後端直接產生在頁面中,而是透過前端產生,前端像手機 APP 一樣透過 API 跟後端溝通,透過要回來的新資料搭配顯示邏輯來渲染,舉個簡單的例子來說,登入之後不需要跳轉至新的頁面並重新載入,只需要透過要回來的資料來更改網頁中的元素(元件)即可~

那為什麼還會需要 SPA 的伺服器端渲染呢? 因為全部網頁都 bundle 起來載入速度慢,頁面中的元素會需要等到 bundle 都下載下來並去後端取回資料後才會呈現,雖然可以透過 webpack code splitting 單元中介紹的,去把自己的程式碼和用到的函式庫分離開來,但仍然沒有傳統後端直接產生來的快速,所以最後理想的狀況就是,第一頁由伺服器端渲染,並在伺服器時就把一些基礎的資料存在狀態中(像是熱門購物推薦清單),之後就都是 client 端的事情,Client 也不需要在重複向後端取得新的熱門購物清單,好處在第一頁的載入和傳統方式相同,之後的優點就是不需要再一直瘋狂向伺服器要整頁的元素了,所以 client 端在操作上也不會有整個頁面重新刷新的問題,這樣兩種的優點都能夠兼得,加上還不是全部的搜尋引擎都能夠爬取 SPA 中的內容,所以 SPA 的伺服器端渲染還是有存在必要!

實作小關鍵

在實作上會遇到的問題:

  1. 由於 SPA 是由狀態來決定元件的顯示,那伺服器端的狀態該如何決定呢? 後端渲染出來的會不會和前端渲染出來的有差異? (RWD?)
  2. 如果有使用狀態管理的函式庫像是 Redux 需要特別處理嗎?
  3. 前端 SPA 用的路由會影響嗎? 畢竟伺服器端並沒有 SPA 的路由

渲染時 react 會幫我們確認 markup 的 checksum 是否相同,若不同就會在 client 端重新渲染一次,若是如此,便失去了做 SSR 的意義,解決上後端 redux 的 SSR 可以利用初始或運算後的狀態去渲染頁面,但之後需要將這個狀態也傳回前端,前端接收到之後再重新產生 store 傳入 APP 中。

另外前端如果有用到像是 window.innerWidth 這種操作去做條件渲染時就一定會有問題,不正確的狀態或是沒考慮過的操作都會 markup 的 checksum 有錯誤,要特別謹記,若有用到 router 的部分不管是前後端都要使用 match 來避免前後端不一致~

Server Side Rendering Issues

  1. React-Router 3?
    要利用 match 然後使用 RouterContext,還有一些情境可能會用到 StaticRouter

  2. Redux?
    先用初始狀態去做出一個 Store,如果要加上從後端計算後的資料,那就去 dispatch 相關 action 然後從取得資料後的新 Store 中重新取得狀態,最後在 renderToString 時把元件轉成 html 的字串,把先前取得的狀態整合進去,就是已經帶有相關資料的狀態及 render 後的元件了

  3. React-Helmet?
    按照文件就是要多寫一行const helmet = Helmet.renderStatic();來避免記憶體洩漏的問題,做快取可能?會有預料之外的狀況,同時若有使用樣版引擎,meta data 中的動態資料整合方式會類似步驟 2

  4. style-component?
    也是需要按照文件去注入相關資訊

  5. 渲染時有用到 innerWidth or navigator 等瀏覽器才有的物件?
    悲劇? 盡量少用啦,因為 Server 沒有這些東西 Orz 真需要用也必須等到元件 mount 上來以後,這就意味著不可以利用這些物件來做條件渲染

  6. Server-side bundle?
    較簡易的實作方式是用 Node.js 語法來撰寫 Server 端,從以上情況看來,Client 端用到的套件幾乎在 Server 端都會再用到,那如果要減少 Server-side bundle 大小來爭取執行速度時,也就是 nodemodule 不一起 build 進來的情況下,webpack 的 external 設定要設一下,上傳到伺服器時,會需要重新 npm install 因為你有用到 Orz 其他要注意的大概是 webpack target: nodelibraryTarget: commonjs2

  7. isomorphic-fetch 在 node 端會需要絕對路徑

  8. 前端路由與後端路由衝突,在頁面導向前,如果是 React Create Script 的專案,前端可以使用 serviceWorker.unregister();

解決方案

  • 建議使用 Next.js,大部分問題有幫我們解決掉,人力不足時,推薦使用。
  • prerender-spa-plugin 這類外掛用於使用 Create Script 時的方案,但很可能會跟不上 React 的升級速度而產生問題,像是 lazy load 的部分。

Next.js

如果沒有太多奇怪的需求,開發初期覺得可以直接使用 Next.js 已經把大部分的問題進行解決,如果有特殊需求官方也都有很好的範例,缺點就是需要自己去組合出自己想要的專案架構,但我想大家都很擅長當複製貼上攻城獅,這樣的學習抄襲曲線應該還算可以接受,當然思考專案的方式可能也會改變成 Next 的方法,不過核心概念還是一樣的~


喜歡這篇文章,請幫忙拍拍手喔 🤣

share