昨天已經先了解了 React 和 Next.js 的關係和差異後,接著我們再進入一個深入認識 Next.js 之前需要先了解的渲染模式的部分。今天先來看看我們平常最常會聽到的 CSR 和 SSR。
(前提說明:這篇是之前在自己的部落格有分享過的主題,所以內容會跟之前的文章內容差不多)
從字面上來看,CSR 指的就是「客戶端渲染(Client-Side Rendering)」,這裡的「客戶端」指的就是使用者的瀏覽器。也就是說,在 CSR 的模式下,HTML 並不是在伺服器產生後直接送給使用者,而是在瀏覽器中透過 JavaScript 動態產生並插入 HTML,最終將畫面渲染出來。
如果想判斷一個網站是否採用了 CSR,可以透過「檢視原始碼」的方式來觀察。
若在檢視時,如果發現 body 裡只有一個空的 div 標籤和一個 script 標籤(如下圖所示),而沒有實際內容,就代表這個 HTML 是個骨架,並沒有被伺服器預先填入資料。也就是說,最後畫面上呈現的內容,是在 JavaScript 執行之後,才在瀏覽器端動態插入進來的。
這樣的渲染模式也正是「客戶端渲染」這個名稱的由來:頁面的資料和畫面,並不是伺服器一開始就準備好的,而是透過 JavaScript 在使用者的裝置上進行組裝與顯示的。
說到 CSR,大家會想到的應該還有 SPA,那這兩者的關係究竟是什麼呢?其實 CSR 並不等於 SPA,因為 CSR 是一種渲染模式,而 SPA 則是一種應用程式的類型,也就是所謂的「單頁式應用程式 (Single-page application)」,其他像是 MPA、PWA 都是一種應用程式類型。
而 CSR 和 SPA 之間的關係則是 「CSR 這個渲染模式,是實現 SPA 的常見方式」,也就是說這兩者彼此的確有關係,但並不等價。
大致上了解什麼是 CSR 後,接著來看看 CSR 的運作方式吧!
如果要用一句話說明,CSR 渲染出畫面的整個流程的話,就會是:
「下載並執行從伺服器 response 回來的 HTML 檔案內引用的 javaScript 檔案,在瀏覽器產生完整的 HTML,以呈現完正的頁面」。
從完整的流程來看的話,則是:
「輸入網址進入網頁 → 對伺服器發送 reques → 伺服器透過 response 把包含基礎內容的 HTML 檔案(只含有掛載完整內容的
<div>
元素)和打包好的 JavaScript、CSS 等靜態資源 發送回來 → 瀏覽器下載並執行 JavaScript → JavaScript 在客戶端動態生成並渲染完整的 HTML 和頁面內容 → 用戶看到完整的網頁」 。
看到這裡,你可能會有一個疑問,「response 回來的 HTML、CSS、JavaScript 是從哪產生的?」,我們可以再回到我們進行開發時的流程下去思考。當我們開發完成後,將網站部署出去前,一定會做的動作就是跑 build 的指令,通常會是 npm run build。當我們執行 build 的指令後,其實可以觀察到會產出類似以下的這些的檔案。
這些檔案也就是我們在瀏覽器上輸入網址後,從伺服器 response 拿到的打包後的檔案,完整的 HTML 內容則是會等到下載並執行裡面的 JavaScript 檔案後,才會被渲染出來。
前面已經了解到什麼是 CSR了,接著來看看與 CSR 只有一字之差的 SSR 是什麼吧!一樣從字面上來看的話,所謂的 SSR 也就是「伺服器端渲染 (Server-Side Rendering)」。換句話說,也就是說 SSR 有別於 CSR 會直接從伺服器的 response 拿到在伺服器上渲染完成的完整的 HTML,所以當嘗試用檢視網頁原始碼來查看原始的 HTML 時,就可以看到和頁面呈現內容一樣完整的 HTML。
例如以下這張圖,可以看到實際肉眼看到的內容,出現在網頁原始碼裡面。
看到這裡有些人可能會突然跑出一個想法,那就是「JavaScript 跑去哪了?」。大家應該都有聽說過,想要讓一個網頁可以達到互動的效果,一定會需要 JavaScript,但是整個靜態的 HTML 都是伺服器幫我們產生,那 JavaScript 會在哪個階段出現呢?這個就不得不提到新舊時代 SSR 的差異。
就如同前面所提到的內容一樣,伺服器會幫我們產出一個完整的靜態 HTML 檔案,所以畫面呈現的部分一定是沒有問題的,但是當我們需要互動時(例如送出表單),一般的靜態的網站並沒辦法做到,那究竟是要怎麼達到這樣的效果呢?這裡就出現了新舊時代 SSR 的差異。
在「舊時代的 SSR 模式」中,如果想要進行送出表單之類的互動操作,會需要伺服器的幫忙,也就是說當我們送出表單時,也會向伺服器發送 request,進行相對應的處理,並且再次 response 一個新的完整 HTML,所以在做互動的動作時,整個網頁會被刷新,如果從 Dev Tools 觀察的話,就可以發現到當我們進行輸入表單的動作或是切換頁面,都會看到有 response 回來一個完整新的 HTML,如同以下的畫面。
與舊時代的 SSR 模式不同的是在「新時代的 SSR 模式」中,SSR 模式不只可以處理靜態頁面,也可以優雅地處理互動操作,因為在新時代的 SSR 模式中,會有一個 hydration 的動作,也就是將 JavaScript 綁定在 DOM 元素上,這就能讓 SSR 不用在做互動的操作時都要請伺服器處理,切換頁面時,也不需要重新向伺服器取得新的 HTML。從這樣的改變來看,其實不難發現到所謂的新時代的 SSR,就是一種結合 CSR 的混合渲染模式。
當我們觀察新時代的 SSR 網站也就可以發現雖然網頁原始碼一樣有顯示當前頁面的完整 HTML,但是當進行切換頁面或提交表單等的動作,並不會再重新取得全新的 HTML,也就會像是以下這個狀況一樣:
知道 SSR 有著舊時代和新時代的差異後,緊接著來看看 SSR 的運作方式究竟是什麼。
簡單來說就是:
「對伺服器發送 request,取得完整的 HTML 顯示在頁面上」。
如果分成舊時代和新時代來看完整的流程的話,則會像是以下這樣:
舊時代的 SSR:
「輸入網址進入網頁 → 對伺服器發送 reques → 伺服器產生完整的 HTML 檔案 → 透過 response 把完整的 HTML 檔案和 CSS 等靜態資源發送回來(在 HTML 檔案中可能會包含處理特效的少量 JavaScript) → 用戶看到完整的網頁」 。
新時代的 SSR:
「輸入網址進入網頁 → 對伺服器發送 request → 伺服器產生完整的 HTML 檔案 → 透過 response 把完整的 HTML 檔案和 CSS 等靜態資源發送回來 → 瀏覽器下載 HTML 中用 script 引用的 JavaScript → 將 event Handlers 綁定到 DOM 上(Hydrate) → Hydrate 完成後,使用者就可以和頁面互動」。
從上面新舊時代的 SSR 的運作流程來看的話,就可以發現從舊時代轉換到新時代的差異,在舊時代的 SSR 模式中,單純就是讓伺服器產生一個靜態的檔案來呈現畫面,如果要做一些互動操作,就必須還要透過伺服器進行處理,所以也就會需要伺服器再回傳一個新的 HTML 檔案;而在新時代的 SSR 模式中,除了讓伺服器產生靜態檔案來呈現畫面外,還會透過 JavaScript 讓頁面可以在瀏覽器中進行操作,這裡也就是 Hydration 的部分,所以只有在第一次進入畫面時,會透過伺服器回傳一個完整的 HTML 檔案,之後切換頁面就不需要再讓伺服器提供完整的 HTML 檔案了。
而我們常常會聽到的 Next.js 和 Nuxt.js 就是新時代 SSR 模式的框架。
這裡我們再稍微深入一點看看在 Next.js 框架中 build 的時候會出現什麼檔案:
從這張圖片可以發現到在 SSR 模式的框架下 build 出來的檔案會比 CSR 來得複雜很多,這是因為在 SSR 模式下,不是單純使用 JavaScript 來產生完整的 HTML 內容,而是需要透過伺服器渲染,而且渲染出完整 HTML 的這個伺服器也並不是靜態伺服器(例如:Nginx),這個伺服器其實是由框架所建構的 node server,也就是說 build 出來的檔案,除了 CSS 等的靜態檔案外,還有關於伺服器渲染的邏輯及伺服器的設定內容。
接著再讓我們更進一步地去從部署流程來思考 CSR 和 SSR 之間的差異。
當我們部署 CSR 專案的時候,我們就只是很單純把 build 出來的靜態檔案放到伺服器(例如:Nginx)上,當瀏覽器對伺服器發送 request 時,就是把對應的檔案 response 回去,接下來就和前面所提到過的一樣,會去下載並執行 HTML 中 script 所引用的 JavaScript 檔案,接著在瀏覽器渲染出完整的 HTML。
而部署 SSR 的專案時,就不是單純的把靜態資源放到伺服器中,而是需要透過 Next.js 或 Nuxt.js 提供的指令,將 node server 給建立起來,因為產生出完整 HTML 的伺服器是 node server,即使是部署到 Nginx 中,也不是由 Nginx 來產生完整的 HTML,還是會需要透過 Nginx 將 request 轉送給 node server,才能產生出完整的 HTML 來回應給伺服器。
最後再來回顧一下前面說提到的 CSR 及 SSR 的特點,也總結一下它們各自的優缺點!
CSR 由於伺服器回傳的 HTML 是一個不含完整內容的 HTML ,需要在瀏覽器中透過下載並執行 javaScript 檔案,才會產生完整的畫面,所以第一次進入頁面的時候,會需要等待一些時間,才會看到完整的畫面,也就是說第一次進入畫面時,會看到白色的畫面一下子,才會出現完整的畫面,但是在之後,不論是對畫面進行操作或是切換頁面,都不需要再對伺服器發送 request,所以切換頁面和進行操作時,整體呈現會比較流暢。
SSR 由於是在伺服器中產生完整的 HTML 給瀏覽器呈現,所以首次進入頁面,不會顯示白色的畫面,但是在舊時代的 SSR 模式中,因為沒 JavaScript 的幫助,所以每次進行操作或是切換頁面時,都要重新和伺服器請求一個新的 HTML,整體的操作過程也就比較不流暢,因為會有刷新頁面的過程;但在新時代的 SSR 模式中,則是因為有 JavaScript 的幫助 (也就是前面所提到的 Hydration),使得在首次拿到完整的 HTML 之後,接著在操作及切換頁面的動作中,就可以透過 JavaScript 讓整體流程更順暢,不用一定要藉由伺服器的幫助才能進行,也就是說新時代的 SSR 結合了 SSR 和 CSR 的優點,但也因為新時代的 SSR 讓伺服器不只是「產生 HTML」,而是「執行一整個 React 應用來產生畫面」,也就讓伺服器負荷與舊時代的 SSR 相比來得高,但這也換來了更彈性、更強大的前後端整合能力。
最後也整理了一個對照表給大家參考
今天 CSR 和 SSR 認識的部分就先到這裡,明天再繼續 SSG 和 ISR 的部分。