# [學習筆記] SSR、CSR、SSR with Hydration 差別 ## Server-Side Rendering ### SSR 伺服器端渲染,表示伺服器收到使用者的請求後,在伺服器生成完整的 HTML。 因為生成 HTML 的時候會在伺服器端先取得內部或外部 API 資料,所以相較於 CSR 從瀏覽器端取資料的模式取資料的模式,SSR可以省去多次的來回往返。 ![image](https://hackmd.io/_uploads/B1p4NueIT.png) SSR 伺服器端渲染或稱後端渲染,並不是為了解決 CSR 問題而存在的也不是什麼新奇的概念。 當使用者請求瀏覽頁面時,後端伺服器可能就已經包含了從資料庫或其他 API 拿取資料的動作,並在後端運算渲染完最終的初始頁 HTML 後,才回應至客戶端的瀏覽器,所以瀏覽器就可以依照 HTML 繪製出初始畫面,並同時等待 JS 下載完畢後執行後續需要進行網頁互動的流程。 在網際網路與瀏覽器剛起步時,使用者的客戶端 (Client) 是與伺服器 (Server) 直接請求網頁檔案,例如 index.html,而檔案的內容即為 HTML 格式的網頁原始碼,並讓瀏覽器進行畫面的渲染繪製,我們先不考慮 JavaScript 進行互動與動態操作網頁的 HTML 元素,這裡的重點是──使用者請求某個網頁頁面後,所接收到 HTML 即是瀏覽器進行畫面渲染的最終網頁原始碼,由後端伺服器吐出最終的 HTML,雖然不是靠後端語言渲染 HTML ,但這不就也是伺服器端產生 HTML 的一種嗎! 再後來,隨著程式語言的發展,PHP、JSP 與 ASP/ASP.NET 等後端語言都是以 SSR 也就是伺服器端渲染為主,再回傳至前端。這一系列由前端發送網頁請求,後端接受到後透過後端程式碼在伺服器端執行、拿取資料庫資料等操作都在伺服端運算渲染完,再回傳至使用者的瀏覽器進行畫面的渲染繪製,也就稱之為伺服器端渲染,亦稱後端渲染。 ## SSR 的 render 時間軸 ![](https://hackmd.io/_uploads/S1VRJ2iZa.png) 在伺服器 render 完 HTML 後傳給使用者,這時候因為已經有完整的 HTML,所以很快就來到 FCP (First Contentful Paint,使用者首次看到頁面上重要內容的時間。) 接著要下載並且執行瀏覽器端互動所需要的 JavaScript,完成後就來到了 TTI (Time to Interactive,使用者首次可以跟頁面互動的時間點)。 ## SSR 的優缺點 優點: * 需要的 JavaScript 比較少,SSR 已經在伺服器端把 render 的工作做完了,SSR 方案中瀏覽器端需要的 JavaScript 理論上會比較少,所以會比較快達到 TTI。 * 有更多的 JS 預算可以留給其他第三方 JS 使用。 * SEO 較佳,因為 SSR 產生的完整 HTML 可以很容易的被爬蟲解讀,不需要想辦法執行 JavaScript。SEO 也是大部分的人會考慮 SSR 方案的最主要原因之一。 缺點: * 較慢的 TTFB (Time to First Byte,從瀏覽頁面的動作開始到瀏覽器收到第一個 byte 所需要的時間),因為在伺服器產生完整的 HTML 很花時間。如果同時有許多人造訪 server 造成負擔很重,或是有一些非常慢的 API,都有可能讓 server 的回應速度非常慢。 * 互動性體驗差,因為 SSR 的頁面在每次互動之間都要重新讀取頁面,這在使用體驗上就不如 CSR 的頁面順暢,也是現代 web app 大多數會採用 CSR 方案的主要原因。 ## Client-side rendering ### CSR 名為客戶端渲染,表示所有的頁面渲染都透過瀏覽器端的Javascript完成。 所有邏輯、取資料、路由、template都在客戶端處理。 ![image](https://hackmd.io/_uploads/rkXI4dl8T.png) 可以發現到因為 CSR 的特點,首頁的網頁原始碼 `<body></body>` 中,僅有 `<div id="app"></div>` 這個元素,通常這個元素就是前端框架要準備 Mount 的根元素或容器,在包含 Vue 程式碼的 JavaScript 檔案尚未下載完畢與執行前,這個容器仍舊是空的,所以也就會導致網頁是一片空白的情況。 如下圖的流程,需要一直到 Vue 程式碼下載且載入完畢後,才能開始做請求初始資料與渲染 HTML 的動作,如果今天網路環境很差,伺服器雖然已經回應回來 HTML,不過在等待 JavaScript 檔案下載的過程中白畫面依舊會持續一段時間,直到完成下載與執行渲染後觸發畫面更新。 ![image](https://hackmd.io/_uploads/r1V-rOg8a.png) CSR 的 HTML 只需要一個簡單的根節點: ```htmlembedded! <div id="root"></div> ``` 頁面上所有的更新都透過 Javascript 來實現。 所以請看以下例子,用Javascript每秒更新一次頁面上的時間: ```javascript! function tick() { const element = ( <div> <h1>Hello, world!</h1> <p>It's {new Date().toLocaleTimeString()}.</p> </div> ) ReactDOM.render(element, document.getElementById('root')) } setInterval(tick, 1000) ``` :::success 客戶端渲染,指的「渲染」就是在客戶端做渲染網頁的動作,瀏覽器依據變動進行畫面的更新,也因為這個特性,框架可以做的到僅渲染 Javascript 的部分,從而讓瀏覽器僅更新部分的畫面,達到更好的效能與體驗。 然而 CSR 最常為人詬病的一項缺點就是搜尋引擎最佳化 (Search Engine Optimization, SEO),一個網站上線後為了能在搜尋引擎上能有更好的排名與曝光,其中一個方式就是要為網站進行搜尋引擎最佳化的配置。 SEO 配置與優化在 CSR 上比較難去實現,因為 CSR 是在客戶端進行資料請求後渲染,再由瀏覽器做畫面更新,導致搜尋引擎的爬蟲所蒐集與索引到的網站網頁,並不包含客戶端所需要即時請求的資料,進而讓爬蟲無法解析到這些資料可能含有關鍵字等索引。雖然現今搜尋引擎的爬蟲遇到 CSR 類型的網站有部分能解決首次資料載入的問題且能收錄到資料,但仍有一些小細節仍不夠友好,綜觀來說 SPA 並不方便也不利於做 SEO 配置。 要解決 SEO 的問題,可以採用預渲染 (Pre-rendering) 的方式,讓網頁請求送達伺服器端後,返回的頁面即為已經包含資料的網頁 HTML,常見的有 Server-Side Rendering 和 Static Site Generation 等技術來解決 SEO 配置。 ::: ## CSR 的 render 時間軸 ![](https://hackmd.io/_uploads/ryHZItiWp.png) 在HTML下載完之後,使用者可以看到頁面上的重要內容的時間點FCP(First Contentful Paint,第一次豐富的內容畫面)發生。 因為CSR的頁面通常只有一個根節點,所以這個時候畫面只會呈現一片空白。 (可以用 spinner、skeleton 之類的來墊檔,讓體驗好一點) 接下來會開始下載一大包 Javascript bundle,下載完之後要解析並執行 Javascript,最後才能 render 畫面。 這些都完成之後才會到 TTI(Time-To-Interactive,使用者首次可以跟頁面戶動的時間點) 在TTI之前,使用者是無法對畫面做任何動作的。 ## CSR 的優缺點 優點: 頁面更新或換頁都不需要刷新,在使用體驗上相較傳統的 SSR 應用會順暢多。 像是 Facebook 網頁版,大部分的使用者互動都不需要刷新頁面,非常順暢,使用起來就像是一個原生的 APP 一樣。 缺點: 載入速度通常較慢,尤其在低階的行動裝置上。 因為 CSR 要等待 Javascript 的下載及執行 render,所以 CSR 頁面載入的前幾秒,頁面上會沒有東西或是只有一些骨架,使用者必須要等待一段時間才能看到頁面的內容。 SEO 會較差,因為 CSR 的頁面對於爬蟲是比較不友善的。 雖然爬蟲有辦法執行 JavaScript,但爬蟲也有一些 [JavaScript render 頁面的限制](https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript?hl=zh-tw)。你可以用[行動裝置相容性測試](https://search.google.com/test/mobile-friendly)這個工具來測試一個 CSR 的頁面會如何被爬蟲 render 。 你可以將[網站資源新增至 Search Console](https://search.google.com/search-console/welcome),你必須先證明自己是網站 (或網站的特定部分) 的擁有者,才能將該網站加入 Search Console 帳戶。你可以建立包含整個網域的資源 (例如 example.com),也可以建立只含單一分支的資源 (例如 example.com/clothing/)。 ![](https://hackmd.io/_uploads/SJYdnYiba.png) ## 如何優化 CSR 的效能 1. 保持 [JavaScript bundle 體積在預算以內](https://medium.com/@addyosmani/start-performance-budgeting-dabde04cf6a3)。頁面初次載入所需的 **JavaScript bundle 大小維持在 100 - 170KB** 是一個不錯的參考值。 3. 使用 preload 提前 JavaScript bundle 的下載。 > preload 告訴瀏覽器:「這份資源對目前的頁面是必要的,請用最快的速度下載此資源。」 使用方法如下: ```javascript! <link rel="preload" as="script" href="super-important.js"> <link rel="preload" as="style" href="critical.css"> ``` as 是用來指定資源的類別的。這個屬性需要指定,不然可能會重複下載同一份資源。 preload 對瀏覽器有「強制作用」而非「建議」,所以你必須很確定它是真正重要的資源。 雖然瀏覽器可以先掃一遍 html 提早發現資源,但是有些「隱藏」在 CSS/JS 內的資源就沒辦法了。這時候用 preload 就非常有幫助。例如: CSS 中的字體檔。 script 中動態載入其他 script/CSS 等。 3. 使用 code splitting 拆分 JavaScript bundle。 保持 JavaScript bundle 在預算內是非常困難的事情,因為 JavaScript bundle 的大小,通常會隨著應用的開發越長越大。其中一個可行的做法是採用 code splitting 的方法,也就是只在真正需要某個 JavaScript 的時候才去下載拆分出來的片段。[Webpack 等打包工具支援 code splitting](https://webpack.js.org/guides/code-splitting/)。 如果想看一些 code splitting 的真實案例,可以參考來原作者[寫的這篇網頁載入效能優化](https://www.shubo.io/optimize-loading-speed/) (Web Performance Optimization) (加速 30% 真實案例分享) ## SSR with Hydration SSR with Hydration,也被稱為 Universal Rendering,是一種透過 hydration 結合了 CSR 與 SSR 的方案,其 render 的流程是: ## SSR with hydation 的 render 時間軸 ![image](https://hackmd.io/_uploads/ByeU2XNIp.png) 1. 在伺服器端產生靜態 HTML 並傳送給使用者。 1. 接著在客戶端透過 hydration 的過程讓網頁具有互動性。 1. 由客戶端的 JavaScript 接手後續的 render 工作。 #### SSR with Hydration 兼具了 SSR 的快速 FCP、SEO 友善,同時又有 CSR 的高互動性。 ![image](https://hackmd.io/_uploads/BJjC27VUT.png) 1. 首先使用者的請求會透過伺服器處理,產生完整的 HTML 以後,用來 render 的資料和 JavaScript 一起被嵌入 HTML,傳送給客戶端。因為已經有完整的 HTML,瀏覽器收到以後可以很快的畫出頁面,達到如同傳統 SSR 的快速 FCP,也就是使用者首次看到頁面上重要內容的時間。 1. 接著瀏覽器會下載 JavaScript bundle,下載完並解析 JavaScript 後,客戶端會執行 hydration 的步驟,完成後達到 TTI,也就是使用者開始可以跟頁面互動。 1. 之後的 render 工作則會由客戶端的 JavaScript 接手,達到 CSR 的高互動性。 ### Hydration 是什麼? Hydration 指的是在客戶端透過 JavaScript 讓伺服器端產生的 HTML 加上 event handler (事件處理器),使其獲得互動能力的一種過程。 在完成 hydration 之前,使用者沒辦法和伺服器端產生出來的頁面互動。必須要等待 hydration 完成,使用者才有辦法跟頁面互動。 ## 結論: CSR => **Client 的拿到的是 html 的框架,但內容都是空的** SSR => **Server 的話就是客戶端拿到的已經是寫好的 html** SSR with hydation => **起初拿的 html 就是完整的,之後的 CSR 會交給 hydation** 本質上都要架 Server,只是看渲染是由前後端哪一端做。 * 純SSR是透過同步的HTTP協定拿到 HTML,如果要更新資料,要靠刷新頁面,不能動態更新,就是古早那種<form action="POST">,然後按下去submit會跳頁,這種就是很純的SSR。 * 現在 Nuxt 或是 Next 這類 SSR+CSR 的框架本質上還是前端做 Server,他們還是依靠 API 去取得資料,API大部分都是透過AJAX去取得,真正的 SSR 是不需要靠 API 的。 他們只有一開始是 SSR,當客戶取得 Javascript 之後,把#app的div換掉,就會變成 CSR ,所以 Nuxt 只有一開始會拿到靜態,後面會自己加載 Javascript,就會轉成 CSR,這種又稱為 hydration。 一開始就會有 `<div id="app">`,vite ssr 是在中間加入一個標示字符,SSR 會去把裡面整段標示字符換成 html,之後變成 CSR 的時候,再把 VDOM 掛上去 ![](https://hackmd.io/_uploads/ByJNK13b6.jpg) ※ 資料來源:https://www.shubo.io/rendering-patterns/#csr-client-side-rendering ※ 資料來源:https://ithelp.ithome.com.tw/articles/10291291