前後端分離或是在一起間接影響網頁渲染的方式,常見的渲染形式會有用戶端渲染 (CSR) 和伺服器端渲染 (SSR) 兩種。
這就好像男女朋友是否同居一樣,會大大影響兩人的日常生活型態,分開時兩者的自由度都較高,在一起同居時總會需要考慮彼此狀態。
前後端要分離或是在一起? 渲染的方式該怎麼決定? 小編覺得也各自有各自的優缺點,接下來就來開箱 CSR 跟 SSR 吧。
Client Side Render (用戶端渲染)
隨著瀏覽器的效能變好,帶有顯示邏輯的渲染可以轉移到瀏覽器端,於是也就出現單頁應用程式 Single Page Application (SPA) 與 Client Side Render (CSR) 的概念。
Single Page Application (SPA) 不同以往產生完整 HTML 的做法,會將網頁都打包 (bundle) 後才在用戶端執行,其中 bundle 可以想像成是 APP 的安裝與執行檔。
在 SPA 中畫面中元素不再直接產生在頁面中,初始時需要 CSR 的部分會是空白的,改由透過 API 要回來的資料和 bundle 檔在前端動態產生渲染畫面。
舉個簡單的例子來說,登入之後不需要跳轉至新的頁面並重新載入,只需要透過要回來的資料來更改網頁中的元素或元件即可。
- 優點
- 操作體驗較接近真實 APP,頁面間切換速度極快
- 伺服器端壓力較小
- 缺點
- 第一次載入時間較慢,但可以透過 Code Splitting 優化
Server Side Render (伺服器端渲染)
HTML 由 Server 端產生,所以用戶看到的就是最終版 HTML,會有三種預渲染形式
- 靜態生成 (Static Generation): 透過 Pre-rendering 工具預先渲染頁面產生靜態檔,HTML 在編譯階段就會生成,以小編的部落格來說就是將 Markdown 語法編譯成 HTML 的靜態檔案,在用戶請求之前頁面就已經準備並可被快取,這種渲染方式也可以跟用戶端渲染搭配著使用來加入額外資訊
- 伺服器端渲染 (Server Side Render):在每次用戶請求時,會動態生成 HTML 效能上會比靜態生成慢,像大家常用的 WordPress 預設就是這種情況
- 增強型伺服器端渲染 (Incremental Static Regeneration): 定期重新進行 SG 避免頁面沒有抓到較新資訊的問題
註: Pre Render 這類外掛適用於 CRA 專案但可能會跟不上 React 新功能所產生的問題
- 優點
- 第一次載入時間較快
- 在 CSR 沒特殊設計的情況下 SEO 較佳
- 缺點
- 操作體驗較 CSR 差
- 伺服器壓力較大
SPA 伺服器端渲染
由於網頁需等待 Bundle 完整載入後才會渲染,速度較慢即使透過 Code Splitting 去把程式碼和用到的函式庫分離開來,但仍然沒有 SSR 產生來的快速。
目前非所有搜尋引擎都能爬取 SPA 中的內容,所以理想狀況是第一頁在伺服器先進行靜態生成或伺服器端端渲染
- 前端不需等待向後端取得資料的時間 (ex 熱門購物清單)
- SPA 在操作上不會有整個頁面重新刷新的問題
Server Side Render 常見問題
伺服器端較簡易的實作方式是用 Node.js 語法來撰寫,理論上前端用到的套件幾乎在後端都會再用到,在實作伺服器渲染時可能會遇到的問題:
- 由於 SPA 是由狀態來決定元件的顯示,那伺服器端的狀態該如何決定呢?
- 後端渲染出來的會不會和前端渲染出來的有差異? RWD?
- 如果有使用狀態管理的函式庫像是 Redux 需要特別處理嗎?
- 前端 SPA 用的路由會影響嗎?
- 伺服器端 Bundle Size 變大
渲染時 react 會幫我們確認元素的 checksum 是否相同,若不同就會在 client 端重新渲染一次。
前端有用到像是 window.innerWidth 去做條件渲染時會有問題,因為 Server 沒有這些東西,必須等到元件 mount 後,意味著不可以利用這些物件來做條件渲染,不正確的狀態或是沒考慮過的操作都會讓 checksum 有錯誤,要特別謹記。
後端做 Redux 的 SSR 會搭配初始狀態加上計算後的資料產生 Store 並渲染,接著在 renderToString 時把元件轉成 html 字串並整合狀態,前端收到後再重新產生 store 傳入 APP 中。
若要減少 Server-side bundle 大小來爭取執行速度時,webpack 的 external 要設定,其他要注意的大概是 webpack target: node
跟 libraryTarget: commonjs2
。
套件們使用時需要注意的事項:
- React Router 若有用到 router 的部分不管是前後端都要使用 match 來避免前後端不一致。
- React-Helmet: 按照文件就是要多寫一行
const helmet = Helmet.renderStatic();
來避免記憶體洩漏的問題,同時若有使用樣版引擎,meta data 中的動態資料須整合 - style-component: 是需要按照文件去注入相關資訊
- isomorphic-fetch: 在 node 端會需要絕對路徑
- serviceWorker: 路由衝突 CRA 專案前端可以使用
serviceWorker.unregister();
Next.js
Next.js 是由 Vercel 平台開發維護的一個 React Framework,已經把大部分 SSR 問題進行解決,如果有特殊需求官方也都有很好的範例。
- 優點
- 減少多餘的專案設定檔
- File System based 路由,不需額外安裝套件和設定
- Code Splitting
- SSR 支援
- 缺點
- 需要參考範例去組合出期待專案架構,對
複製貼上攻城獅來說學習抄襲曲線應該可被接受 - 思考系統設計跟架構也會需要 Next 風格,不過核心概念還是一樣的。
- 需要參考範例去組合出期待專案架構,對
如果沒有太多奇怪的需求,且是有 React 開發經驗的工程師,開發初期小編覺得可以直接使用 npx create-next-app
開始進行開發。
喜歡這篇文章,請幫忙拍拍手喔 🤣