淺談像是 React 的 SPA (Single Page Application) 前端專案如何實作 Server Side Render,伺服器渲染有哪些好處,又會遇到哪些問題?
Client Side Render (CSR) 簡介
隨著瀏覽器的效能開始越來越好,帶有顯示邏輯的渲染可以轉移到瀏覽器端,於是也就慢慢出現了單頁應用程式 Single Page Application (SPA) 與 Client Side Render (CSR) 的概念。
Single Page Application (SPA)
不同以往在伺服器實作不同頁面的做法,現在會將網頁都打包 (bundle),這裡可以先把 bundle 想像成是 APP 的 apk 檔。
Client Side Render (CSR)
在 SPA 中畫面中的元素不再透過直接產生在頁面中而是前端動態產生,SPA 透過 API 要回來的資料搭配顯示邏輯來渲染畫面。
舉個簡單的例子來說,登入之後不需要跳轉至新的頁面並重新載入,只需要透過要回來的資料來更改網頁中的元素(元件)即可。
Server Side Render (SSR) 簡介
什麼是 Server Side Render (伺服器渲染)? 為什麼 SPA 還會需要實作伺服器端渲染呢?
- 網頁需等待 Bundle 完整載入後才會渲染
- 非所有搜尋引擎都能爬取 SPA 中的內容
雖然透過 webpack code splitting 單元中介紹的,去把自己的程式碼和用到的函式庫分離開來,但仍然沒有傳統後端直接產生來的快速。
常見的解決方法
- Static Generation (SG): 透過 Pre-rendering 工具預先 render 頁面產生靜態檔
- Server-side Rendering: 每次 Request 都即時將資料存在狀態中並 render 後回傳
- Incremental Static Regeneration (ISR): 定期重新進行 SG 避免頁面沒有抓到較新資訊的問題
理想狀況是第一頁在伺服器先進行 SSR,前端就不需等待向後端取得資料的時間 (ex 熱門購物清單),SPA 在操作上也不會有整個頁面重新刷新的問題,這樣兩種的優點都能夠兼得。
Server Side Render 常見問題
在實作伺服器渲染時可能會遇到的問題:
- 由於 SPA 是由狀態來決定元件的顯示,那伺服器端的狀態該如何決定呢?
- 後端渲染出來的會不會和前端渲染出來的有差異? RWD?
- 如果有使用狀態管理的函式庫像是 Redux 需要特別處理嗎?
- 前端 SPA 用的路由會影響嗎?
渲染時 react 會幫我們確認 markup 的 checksum 是否相同,若不同就會在 client 端重新渲染一次。
後端在做 redux 的 SSR 時,可以利用初始或運算後的狀態去渲染頁面,但之後需要將這個狀態也傳回前端,前端接收到之後再重新產生 store 傳入 APP 中。
另外前端如果有用到像是 window.innerWidth 這種操作去做條件渲染時就一定會有問題,不正確的狀態或是沒考慮過的操作都會 markup 的 checksum 有錯誤,要特別謹記。
若有用到 router 的部分不管是前後端都要使用 match 來避免前後端不一致。
- ReactRouter: 要利用 match 然後使用 RouterContext,還有一些情境可能會用到 StaticRouter
- Redux: 初始狀態加上從後端計算後的資料做一個 Store,接著在 renderToString 時把元件轉成 html 的字串,並先前狀態整合進去
- React-Helmet: 按照文件就是要多寫一行
const helmet = Helmet.renderStatic();
來避免記憶體洩漏的問題,同時若有使用樣版引擎,meta data 中的動態資料須整合 - style-component: 是需要按照文件去注入相關資訊
- innerWidth or navigator 等瀏覽器才有的物件: 因為 Server 沒有這些東西,必須等到元件 mount 後,意味著不可以利用這些物件來做條件渲染
- isomorphic-fetch: 在 node 端會需要絕對路徑
- serviceWorker: 前端路由與後端路由衝突,CRA 專案前端可以使用
serviceWorker.unregister();
- Server-side bundle: 較簡易的實作方式是用 Node.js 語法來撰寫 Server 端,從以上情況看來,前端用到的套件幾乎在後端都會再用到。那如果要減少 Server-side bundle 大小來爭取執行速度時,也就是 nodemodule 不一起 build 進來的情況下,webpack 的 external 設定要設一下。上傳到伺服器時,會需要重新 npm install 因為有用到,其他要注意的大概是
webpack target: node
跟libraryTarget: commonjs2
。
解決方案上
- 建議使用 Next.js,大部分問題有幫我們解決掉,人力不足時,推薦使用。
- prerender-spa-plugin 這類外掛用於使用 Create Script 時的方案,但很可能會跟不上 React 的升級速度而產生問題,像是 lazy load 的部分。
Next.js
Next.js 是由 Vercel 平台開發維護的一個 React Framework,已經把大部分的問題進行解決,如果有特殊需求官方也都有很好的範例。
優點
- 減少多餘的專案設定檔
- File System based 路由,不需額外安裝套件和設定
- Code Splitting
- SSR 支援
缺點
- 需要參考範例去組合出期待專案架構,對
複製貼上攻城獅來說學習抄襲曲線應該可被接受 - 思考系統設計跟架構也會需要 Next 風格,不過核心概念還是一樣的。
如果沒有太多奇怪的需求,且是有 React 開發經驗的工程師,開發初期小編覺得可以直接使用 npx create-next-app
開始進行開發。
喜歡這篇文章,請幫忙拍拍手喔 🤣