React 洗菜 — Single Page Application(S.P.A.)&渲染機制

Lee Luciano
8 min readJun 29, 2023

--

這次要討論的主題是Single Page Application (S.P.A),我知道現在早就是SSR/SSG 的天下了,但我問過許多公司的面試官為何要使用 Nextjs 或者 Nuxt 這類的框架,沒有一個回答的出合理的解釋,尤其當我提及(CSR)的缺點及提升的方法時,能回答出來的更是少之又少,多半只是想表現公司方對於新技術的接受度高,但這樣的結果技術債只會越疊越高。

那麼,我們先來聊聊多頁應用程式 (MPA) 與單頁應用程式 (SPA)架構之間有何不同吧!

多頁應用程式 — MPA(Muti Page Application)

是由多個HTML頁面組成的網站,大部分在伺服器上呈現。當您切換到新頁面時,瀏覽器會從伺服器請求一個新的HTML頁面。傳統的MPA框架還包括Ruby on Rails、Python Django、PHP Laravel、WordPress…等靜態網站構建工具。

單頁應用程式 — SPA(Single Page Application)

是由一個單一JavaScript應用程式組成的網站,它在用戶的瀏覽器中加載並在本地呈現HTML。SPA也可以在伺服器上生成HTML,但是SPA獨特的地方在於它能夠在瀏覽器中運行您的網站作為JavaScript應用程式,以在切換頁面時長出新的HTML。Next.js、Nuxt、SvelteKit、Remix、 React都是SPA框架的示例。

渲染機制

作爲 React 的菜鳥,應該要知曉自己使用的工具是如何運作的,我想如果大部分半路起家直接學 React 的勇者,可能會不清楚老時代的工具,讓我來分享一下老時代的做法吧。

老時代的作法 — MPA(Muti Page Application) — SSR(Server Side Rendering)

用戶瀏覽你的網站,你網站端的 server 收到請求就會做右方 server 端的處理,最後回傳產生的靜態 html 檔案給用戶,那麼用戶要更新頁面怎麼辦呢?

  1. 透過 javascript / JQuery 的 ajax 請求,再由 server 去決定要抽換的 html template。
  2. 全部交給後端決定需要更新抽換的部分 (例如 php, ejs…)。

以上結果造成我們常講的義大利麵結構體,也就是我會盡量遠離的案子,因為通常 💩 code 很多。

普遍SPA 框架的作法 — SPA(Single Page Application) ex: React, Vue, Angular — CSR(Client Side Rendering)

用戶一樣瀏覽你的網站,網站端透過 CDN 處理 default 打包出來的空的html,也就是一般我們在框架中看到的那個index.html,回傳給用戶瀏覽器,這個畢竟是空的html所以無畫面的問題不會持續太久,然後會依照你index.html 內的 script tag 去和 CDN 層拿取 main.js 並掛在於 html 中,這時候就能看到畫面了,一般來說會下一些loading skeleton,來讓用戶有比較好的體驗,最後就是等打出去的 api 回傳 api server 的資料再渲染出正確的頁面了。

  • 優點
    大幅縮短了等待無畫面的時間,透過 loading skeleton 的狀態明確告知用戶目前的狀態,方便前端的code能夠模組化,能透過回傳資料共用相同的 template,首次加載完js之後,後續的響應速度極快,大幅提升用戶的體驗。
  • 缺點
    metadata 沒辦法在一開始產生html時去給爬蟲爬,SEO相對弱勢,如果交互層面需要打多個 api 那會造成頁面超多 loading skeleton,等待時間也會和層套的 component 有所關聯,如果頁面需要的 api 很多,會讓用戶首次等待時間較久。

新型態的SSR — (SPA的SSR)

上面就是普遍對SPA和傳統渲染機制的差異比較,但事實上傳統的MPA就是一種SSR(Server Side Rendering)的機制,但不等於現在流行的 Nextjs, Remixjs, Nuxt…, 這些框架走的是新型態的機制如下:

這邊以Next.js為例

新型態的SSR不同於純server渲染,也擁有著如 SPA 一樣快速渲染的能力,此外還解決傳統缺少交互的缺點,這裡是以 Next 為例,它還額外有 SSG/ISR 等更進階的渲染特性可以提供選擇,有想要深入瞭解的可以點擊連結

這裡會看到多了一個僵直時間,簡單來說就是畫面已經有了,可是 js 還沒完全載入,會使得畫面上的按鈕、交互沒有反應,要等到完全載入後功能才會正常,這個部分就是目前前端框架中最熱門被提及的渲染效能提升議題Hydration,也就是後續更新所提及的server component,但這裡不細講,主要還是給大家多了解新型態 SSR 的作法與舊世代的差異,簡單來說新型態的作法還是以SPA為主要概念,來建構一套專屬的SSR方案。

新世代的MPA — Astro

這裡分享一個比較特別的框架Astro,它仍然以MPA為基底去整合SPA的特性,Astro與其他MPA框架也有所不同,主要區別在於它使用JavaScript作為其伺服器語言和執行,傳統的MPA框架會讓您在伺服器上使用不同的語言(如Java、PHP等),並在瀏覽器上使用JavaScript。而在Astro中,您只需要編寫JavaScript、HTML和CSS,就可以在伺服器和客戶端上呈現UI組件(ex: React和Svelte)。

這個感覺非常像Next.js和其他現代網頁框架,但具有更傳統MPA網站架構的性能特性。

MPAs vs. SPAs

有三個主要差異,這部分取自Astro 官方文件:

渲染層面

在MPAs中大部分頁面的HTML在伺服器上呈現,而在SPAs中,大部分 HTML 是通過在瀏覽器中運行JavaScript本地呈現的(我想這部分就如同上述舉例的CSR框架)。這對網站的行為、性能和SEO有很大影響。

將HTML在瀏覽器中呈現可能聽起來比從遠程伺服器請求更快。然而,事實恰恰相反。除非使用伺服器呈現,否則SPA在首次頁面加載時的性能表現一直較慢於MPA。這是因為SPA需要在瀏覽器中下載、解析和執行整個JavaScript應用程式才能呈現頁面上的任何HTML。然後,您的SPA可能還需要取回遠程數據,這會在頁面完成加載之前引入更多等待時間。

大多數SPA框架會嘗試通過在首次頁面加載時添加基本的伺服器呈現來緩解這個性能問題。這是一個改進,但由於您的網站現在可以以多種方式和多個環境(伺服器、瀏覽器)呈現,因此引入了新的複雜性。這還引入了一個新的「uncanny valley」問題,即您的網站顯示已加載(HTML),但由於應用程式JavaScript邏輯仍在後台加載,因此無法響應(如我上述圖表內 js loading 造成的僵直時間)。

MPAs在遠程伺服器上呈現所有HTML,通常不需要太多(如果有的話)執行JavaScript。這使得MPAs比SPAs在首次加載體驗上更快,這對於以內容為重點的網站至關重要。但這帶來了一個權衡:對於長期使用的用戶體驗,在首次頁面加載之後,頁面導航無法從本地呈現中獲益太多。

路由部分

您的網站路由器位於哪裡?在MPA中,每個對伺服器的請求都決定了要回應的HTML,因此路由邏輯位於伺服器上。在SPA中,您的路由器在瀏覽器中本地運行,並在未觸及伺服器的情況下渲染新的頁面。

這與上述描述的權衡類似:MPAs提供了更快的首次加載體驗,而SPAs在JavaScript應用程式完全在瀏覽器中加載後可能在第二或第三次頁面加載方面更快。

SPAs 還可以在頁面導航方面提供更強大的過渡效果,因為它們對頁面渲染有很大的控制權。為了達到這種功能,MPAs 利用像 Turbo 之類的工具,通過在瀏覽器中控制導航來模仿客戶端路由。HTML仍然在伺服器上呈現,但Turbo 現在可以展示與 SPA 中客戶端路由類似的無縫過渡效果。

狀態管理

對於處理複雜的多頁狀態管理(例如:Gmail)的網站,SPAs是更優越的架構。這是因為SPA將整個網站作為單一JavaScript應用程式運行,從而使應用程式能夠在多個頁面之間保持狀態和記憶。像收件箱和管理儀表板這樣的互動式數據驅動體驗在SPAs中表現良好,因為網站本身本質上就像是一個「應用程式」。

總結

在實際情況下,許多網站將這兩種架構結合起來,利用它們各自的優點。例如,可以使用MPA架構構建整個網站,並在需要較強交互性或狀態管理的特定部分使用SPA組件,上述新興的框架也都有相應的渲染方案來做調整,取用哪種方式渲染幾乎都能夠實現,但是重點應該要擺在如何權衡,也就是你的產品真的需要用到如此架構和渲染方式才能達到目的嗎?

架構和渲染方式是完全不同的兩個議題,希望這次的分享可以讓大家多了解一點自己開發的框架,期待下次的分享,我會努力更新的。

--

--