淺談 React 的前端專案如何實作 SSR,伺服器渲染有哪些好處,又會遇到哪些問題?
簡介
隨著瀏覽器的效能開始越來越好,帶有顯示邏輯的渲染可以轉移到瀏覽器端,於是也就慢慢出現了 Single Page Application 的概念,和以往散落在好幾頁的做法不同,現在將所有網頁程式都 bundle 成一個檔案,可以把 bundle 想像成是 APP 的 apk 檔,畫面中的元素不再透過後端直接產生在頁面中,而是透過前端產生,前端像手機 APP 一樣透過 API 跟後端溝通,透過要回來的新資料搭配顯示邏輯來渲染,舉個簡單的例子來說,登入之後不需要跳轉至新的頁面並重新載入,只需要透過要回來的資料來更改網頁中的元素(元件)即可~
那為什麼還會需要 SPA 的伺服器端渲染呢? 因為全部網頁都 bundle 起來載入速度慢,頁面中的元素會需要等到 bundle 都下載下來並去後端取回資料後才會呈現,雖然可以透過 webpack code splitting 單元中介紹的,去把自己的程式碼和用到的函式庫分離開來,但仍然沒有傳統後端直接產生來的快速,所以最後理想的狀況就是,第一頁由伺服器端渲染,並在伺服器時就把一些基礎的資料存在狀態中(像是熱門購物推薦清單),之後就都是 client 端的事情,Client 也不需要在重複向後端取得新的熱門購物清單,好處在第一頁的載入和傳統方式相同,之後的優點就是不需要再一直瘋狂向伺服器要整頁的元素了,所以 client 端在操作上也不會有整個頁面重新刷新的問題,這樣兩種的優點都能夠兼得,加上還不是全部的搜尋引擎都能夠爬取 SPA 中的內容,所以 SPA 的伺服器端渲染還是有存在必要!
實作小關鍵
在實作上會遇到的問題:
- 由於 SPA 是由狀態來決定元件的顯示,那伺服器端的狀態該如何決定呢? 後端渲染出來的會不會和前端渲染出來的有差異? (RWD?)
- 如果有使用狀態管理的函式庫像是 Redux 需要特別處理嗎?
- 前端 SPA 用的路由會影響嗎? 畢竟伺服器端並沒有 SPA 的路由
渲染時 react 會幫我們確認 markup 的 checksum 是否相同,若不同就會在 client 端重新渲染一次,若是如此,便失去了做 SSR 的意義,解決上後端 redux 的 SSR 可以利用初始或運算後的狀態去渲染頁面,但之後需要將這個狀態也傳回前端,前端接收到之後再重新產生 store 傳入 APP 中。
另外前端如果有用到像是 window.innerWidth 這種操作去做條件渲染時就一定會有問題,不正確的狀態或是沒考慮過的操作都會 markup 的 checksum 有錯誤,要特別謹記,若有用到 router 的部分不管是前後端都要使用 match 來避免前後端不一致~
Server Side Rendering Issues
React-Router 3?
要利用 match 然後使用 RouterContext,還有一些情境可能會用到 StaticRouterRedux?
先用初始狀態去做出一個 Store,如果要加上從後端計算後的資料,那就去 dispatch 相關 action 然後從取得資料後的新 Store 中重新取得狀態,最後在 renderToString 時把元件轉成 html 的字串,把先前取得的狀態整合進去,就是已經帶有相關資料的狀態及 render 後的元件了React-Helmet?
按照文件就是要多寫一行const helmet = Helmet.renderStatic();
來避免記憶體洩漏的問題,做快取可能?會有預料之外的狀況,同時若有使用樣版引擎,meta data 中的動態資料整合方式會類似步驟 2style-component?
也是需要按照文件去注入相關資訊渲染時有用到 innerWidth or navigator 等瀏覽器才有的物件?
悲劇? 盡量少用啦,因為 Server 沒有這些東西 Orz 真需要用也必須等到元件 mount 上來以後,這就意味著不可以利用這些物件來做條件渲染Server-side bundle?
較簡易的實作方式是用 Node.js 語法來撰寫 Server 端,從以上情況看來,Client 端用到的套件幾乎在 Server 端都會再用到,那如果要減少 Server-side bundle 大小來爭取執行速度時,也就是 nodemodule 不一起 build 進來的情況下,webpack 的 external 設定要設一下,上傳到伺服器時,會需要重新 npm install 因為你有用到 Orz 其他要注意的大概是webpack target: node
跟libraryTarget: commonjs2
isomorphic-fetch 在 node 端會需要絕對路徑
前端路由與後端路由衝突,在頁面導向前,如果是 React Create Script 的專案,前端可以使用
serviceWorker.unregister();
解決方案
- 建議使用 Next.js,大部分問題有幫我們解決掉,人力不足時,推薦使用。
- prerender-spa-plugin 這類外掛用於使用 Create Script 時的方案,但很可能會跟不上 React 的升級速度而產生問題,像是 lazy load 的部分。
Next.js
如果沒有太多奇怪的需求,開發初期覺得可以直接使用 Next.js 已經把大部分的問題進行解決,如果有特殊需求官方也都有很好的範例,缺點就是需要自己去組合出自己想要的專案架構,但我想大家都很擅長當複製貼上攻城獅,這樣的學習抄襲曲線應該還算可以接受,當然思考專案的方式可能也會改變成 Next 的方法,不過核心概念還是一樣的~
喜歡這篇文章,請幫忙拍拍手喔 🤣