稍有經驗的開發者,可能對 SPA、SSR 等術語不太陌生,大概也能知道 SPA 指的是單頁應用程式(SPA, Single Page Application)、SSR 是伺服器端渲染(SSR, Server-Side Rendering)。

可是仔細想想,在前端整個興起之前,主流的 PHP、JSP 不也是伺服器渲染嗎?中間發生了什麼?現在的開發者又為什麼開始想走回 SSR 了?

從頭說起#

剛進入相關領域時寫的是 JSP(Java Server Pages),基本語法跟原生的 ASP、PHP 有點像,藉由 <%%> 標出 Java 程式的範圍,後端進行編譯、運算後,再將處理好的 .jsp 檔回給前端;而與使用者的互動則透過 jQuery 控制。

那時所撰寫的程式經常會在同一隻 .jsp 檔,內容混合著 Java 及 JavaScript 的程式,再加上透過 jQuery 動態操縱 DOM 及 CSS,可以說是真正的「義大利麵程式碼」。

MVC#

於是一些開發者就開始嘗試,將資料與 UI 分離,再撰寫控制器去操縱、計算這兩者,也就是俗稱的 MVC 架構:

  • Model:模型,負責處理資料、演算法、商業邏輯等等
  • View:視圖,欲呈現的畫面
  • Controller:控制器,控制、處理使用者的請求(request)

藉由這樣權責分明的設計,糾纏混亂的義大利麵變成了各自獨立的「義大利餃」,程式的整體架構也得以被這樣的設計梳理整齊。

MVC 帶來了更低的耦合度、更高的可維護性,並且讓專案能被完整的分工。各大語言都有包含此概念的框架被實作出來,開發者可以輕易挑選到合適的 MVC 框架,並快速架構出網站伺服器的雛形。透過 MVC 架構,也算是讓 SSR 的網站程式有個相對理想的程式架構參照。

但有幾個 SSR 的問題,是單靠 MVC 框架無法解決的:

  • 頁面由伺服器產生,每次更新頁面都需要全畫面重新渲染
  • 伺服器取得資料後,需要計算畫面並整頁送出,運算及流量的負荷都太大

這些問題的解決方案,在 Gmail 問世之後出現了一線曙光。

SPA#

Gmail 是 Google 出的電子信箱服務,從 2004 年問世以來,便一直是熱門好用的免費信箱選擇。為什麼要特別提到它?

因為 Gmail 充分利用了瀏覽器的 XMLHttpRequest,透過這個 API 送出新的 Request,取得更多資料,再透過 JavaScript 變更目前畫面上的內容。過程中畫面完全不需要重新渲染,也就不會出現惱人的一片白,整體的使用者體驗大幅提升。

透過 XMLHttpRequest,前述的兩個問題都不再是問題。伺服器只需要提供一個引入其他 JavaScript 的頁面,其他的頁面流轉、資料獲取、DOM 調整全部交給 JavaScript 處理,除了降低伺服器的運算效能消耗及資料吞吐量,還讓網站使用者使用網站有如使用獨立的應用程式一樣流暢,單頁應用程式(SPA, Single Page Application)也因而得名。

由於 SPA 的出現,伺服器端也就不用再處理 MVC 中 View 的部分,而是交給前端來處理。但這樣一來前端要處理的事情就包山包海了,取得資料、處理畫面、頁面跳轉,儼然有種要變成義大利麵程式碼的跡象。

既然 MVC 的架構能有效管理程式碼,那麼就把 MVC 的概念搬到前端來。後端沒有自己的 View,只需要提供資料給前端的 Model,讓前端完成自己的 MVC。

實際上各家的實作、稱呼不見得是 MVC,但同樣都會有管理資料取得、加工處理的 Model,以及負責處理畫面細節的 View。

SPA 的缺點#

SPA 成為新共主,箇中實作互有巧妙,但同樣都是藉由 JavaScript 處理大部分的事情,提高使用者體驗。

但看似美好的 SPA 也不是沒有任何缺陷:

  • 由於需要仰賴前端下載大量 JavaScript 檔案,隨後才計算、渲染畫面,造成第一畫面需要較長的反應時間,在性能較差的行動裝置上更是如此
  • 因為伺服器僅提供載入 JavaScript 的空殼 HTML,SEO(Search Engine Optimization)的表現非常糟糕

剛好這些缺點,都是原本 SSR 的網頁中不存在的問題,但 SSR 網頁的伺服器負荷、使用者體驗也是問題。要是可以摻在一起就好了。

Isomorphic JavaScript#

既然 SPA 的問題只在第一畫面,那就在伺服器先算好第一畫面,其他畫面再透過 JavaScript 動態處理。也就是第一畫面為 SSR,其他畫面是 CSR(Client-Side Rendering),這樣是不是就完美了?

這樣做的代價,就是同一個頁面需要實作兩次顯示邏輯(前端、後端各一次)。幸好 JavaScript 不是一個純粹前端的語言,後端可以跑在 Node.js 中,開發者得以使用大致相同的程式,實現這樣的需求。

這種同一份 JavaScript Code 可以跑在前端與後端的程式設計,叫做 Isomorphic JavaScript(同構的 JS)。也有人叫做 Universal JavaScript(通用的 JS)。

在這樣的概念出來後,前端框架的 SSR 框架也就應運而生了,例如 React 的 Next,及 Vue 的 Nuxt,都是原本前端框架的語法再擴充,讓原先就熟悉框架的開發者,只需要越過相對小的進入門檻,就能滿足第一頁 SSR 的需求。

除了透過 SSR 框架幫你把頁面的第一次渲染即時算出來,在一些資料相對簡單,變動不太大的網站,也可以選擇透過例如 Prerender 之類的工具,事先把網頁全部算好,儲存成靜態頁面,這樣也可以解決 SEO 的問題。

藉由 Isomorphic JavaScript 的設計,及 Prerender 等工具,第一畫面才會出現的問題就能被解決,關於 SPA 及 SSR 的優缺點取捨,自然也就不再是問題。

結語#

「程式開發是一個隨意創造、濫用名詞的世界」。

技術名詞多如牛毛,甚至同一個名詞在不同場景也常有著不同意思。若過度糾結於名詞的定義,造成無法溝通協作,就真的是本末倒置了。唯有開發者清楚知道自己想要、需要什麼,並理解名詞、工具、方法論背後的動機意涵,才能在這個稍嫌混亂的世界裡,找出最適合自己的解決方案。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10224772