你可能會聽過 RWD 或是 SEO,但從工程師口中,還會聽到一些無法理解的名詞:什麼是 SPA? SSR? ISR? CSR? 什麼又是 SSG? RSC? 近代瀏覽器技術蓬勃發展,網頁的渲染效率一直是前端工程不斷精進的課題,因為大家都想要讓網頁超級快,沒有最快只要更快!前端工程世界為了因應人類對於追求速度的渴望,發明了很多提升網頁載入速度的策略和招式,有簡單粗暴且單一的,也有設計華麗的組合技;有發生在瀏覽器的,也有發生在伺服器的,各式各樣。使用者只要知道,從你開始所輸入的內容(資料)到成品(看見),這之間的過程都有可能發生的。
網頁組成最基本的要素,大致上由 HTML、CSS 以及 JavaScript 三種類型的檔案所組成,可以說網頁渲染不可或缺的條件。以 https://google.com 為例,當瀏覽器載入網站時,打開開發者工具,你可以看到有哪些檔案被瀏覽器被載入:
從圖片中的右下角可以看到,有著各式各樣的副檔名,而除了基本的 HTML, CSS 以及 JavaScript 以外,當然還有為了內容更充實的圖片(gif, jpg, png, webp)或多媒體檔案(mp3, mp4),而這類的網頁渲染方式就是非常簡單明瞭:把檔案放在雲端空間中。在 Web 世界,這個「雲端」就是一個網路空間,透過「網址」開放給全世界的使用者瀏覽。
以上這些將「檔案」放在雲端上提供瀏覽的方式產生了一個缺點,就是每一次要修改文章就必須「重新上傳」一遍。想像一下,你修課時用 word 完成的報告,在上傳給助教後發現有錯字需要修正,修正完又得「重新上傳」,每一次都必須如此!由於這種網頁呈現的方式對於內容需要經常變動的網站非常麻煩且不適合,因此相對於動態內容的網站,此種方法就被稱為「靜態網站」(Static Site).
相較於靜態網站是直接將成品(想像成 word 報告)上傳,此種方式是將網站每一頁的半成品放在啟動的「伺服器」上,透過將半成品加工後產生 HTML 來渲染頁面。這些半成品的檔案是具有儲存變數能力的「樣板引擎」,他允許你在報告裡面宣告一個變數,接著從資料庫中抓取人名或電子郵件,最後透過此變數宣告來「動態產生」網站內容。一切的渲染邏輯發生在伺服器上面,而瀏覽器只是將渲染過的精美 HTML 檔案載入進來而已。
想像一下,學期末的老師要列印同學的成績單,由於班上的 100 位學生都是具有相同的科目,只有姓名和成績的不同,因此聰明的老師不需要重複打 100 次的 word 報告,只需要完成一次所謂的「樣板」,並透過宣告變數,例如學生姓名叫做 (name) ,學生成績叫做 (score) ,從資料庫裡面撈資料出來一次列印 100 份就好。
Multipage Application 在當時成為網頁渲染架構的主流,直到現在使用 Wordpress 架設網站的還是非常多,但 2007年 iPhone 的誕生打破了這個主流習慣。原本是每一次換頁或是網址改變都要重新載入整個頁面,在當使用者操作 iPhone 後發現,原生的 App 跟 Web 網頁瀏覽的體感順暢度原來差距這麼大,因為 App 操作的絲滑感讓人類覺得網頁載入很慢又卡頓,換頁還會閃爍或空白的等待時間。
傳統來說,HTML 負責文字內容、CSS 負責美化,而 JavaScript 則負責使用者互動。為什麼是「傳統來說」呢?因為手機的普及打破了這個傳統,讓 JavaScript 從跑龍套的角色,變成渲染 UI 的最大主角,也就是下一個渲染方式:SPA.
傳統來說,原本的網頁技術都被叫做 Website 或是 Webpage, 直到單頁應用程式的出現,才有人開始稱呼 Web App。單頁應用程式全名為 Single Page Application (簡稱 SPA), 原理是說前端網站只載入一個幾乎空的 HTML ,透過載入 JavaScript 將所有的內容渲染上去。對,你沒看錯, JavaScript 也有控制 HTML 和 CSS 的能力!以前 JavaScript 操作網頁上元件(稱為 DOM)的能力有限,在和使用者互動時才會用,例如新增刪除的功能性按鈕或是表單操作等等;但為了應付 App 這波風潮帶來的體驗,網頁功能越來愈複雜,因此「框架」們在前端社群裡大量被發明出來!從前期的 Knockout.js, Backbone.js, Ember.js 到近代的 React, Vue, Angular 等等,都在這10年的前端世界裡大放異彩。
「框架」就是有一批好用的語法或工具函式,可以讓你通過 JavaScript 快速開發 UI ,下圖是 2016-2022 年間的框架滿意度調查:
單頁應用程式(SPA) 原理是通過 JavaScript 在客戶瀏覽器中渲染頁面的方式,因此又被稱為客戶端渲染(Client Side Rendering, 簡稱 CSR)。由於網頁內容幾乎是從 JavaScript 的運作中產生,使用者在切換不同網頁路徑或網址時,是不需要從 server 端重新載入,因此操作上就猶如一個 App 般的絲滑感受。但是,在網頁載入初期僅僅看到空白的 HTML 頁面,會讓 Google 搜尋引擎或網路爬蟲充滿問號;換句話說,如果網路爬蟲沒有將 JavaSciprt 運算過一遍,那麽將永遠看不到網頁內容,但運算 JavaScript 的成本很高,因此 SPA 的缺點顯而易見:
在 SPA 的世界裡, JavaScript 主宰了一切:從 HTML 內容、CSS 樣式(稱為 Inline Style)、頁面路由的跳轉(Routing),都是經由 JavaScript 的運算來產生。為了提升操作體驗,這個包含多功能的 JavaScript 檔案勢必會超!級!大!如果 JavaScript 載入要等很久,那使用者一踏進來所看到的畫面將會是一片空白,而且要等 JavaScript 載完、運算完畢後才能看到畫面,但使用者在網頁上衝浪畢竟是沒有耐性的。
下一個技術就是在解決這個大檔案的問題。
如果一個 SPA 網站的 JavaScript 這麼巨大,為何我們不像 MPA(Multipage Application) 一樣,根據每一個頁面來切分程式碼要載入的部分呢? Code Splitting 就是在利用這個概念,與其在一開始就全部載入檔案,不如把這個大檔案全部切開,等使用者有進入該頁的時候再載入吧。
Code Splitting 技術其實沒有這麼容易達成,必須依賴開發工具或打包工具來完成,這些常被稱為「前端工具鏈」;而工程師透過手動的方式,在程式碼裡使用 dynamic import(動態載入)頁面的技巧,來達到 Code Splitting 的目的。
雖然 Code Splitting 解決了大一檔案很大包的痛點,但 SEO 不佳的問題依然存在:網絡爬蟲的世界裡,第一頁就是空白的。既然如此,那麽第一頁不要空白就好了吧?
簡稱為 SSR,透過改善第一頁需要載入 JavaScript 才能看到內容的問題。SSR 的原理,讓第一頁 HTML 的網頁內容在伺服器中執行 JavaScript 的運算後預先渲染完成,等使用者或爬蟲造訪時直接把 HTML 送出去,這樣就會先看到精美的網頁內容;同時,網頁在背地裡偷偷的把剩下的 JavaScript 給載入完畢。SSR 和上述的 SPA 差異只在第一頁,SSR 是先渲染完畢的成品,而SPA是空白的。
值得注意的是,第一頁從伺服器丟過來精美的網頁是「沒有任何互動」功能的,是由於 JavaScript 並未載入的緣故。因此,從網頁看到靜態內容的 HTML 到能具備互動功能的狀態稱為 Hydration. 網頁在經過 Hydration 後才有辦法像 SPA 一樣跳轉頁面和執行按鈕功能唷!
Static Site Generation 簡稱 SSG,渲染時機發生在程式碼被「打包」的時候,因此又被稱為「預先渲染(Pre-rendering)」的策略方式:亦即在程式碼打包的過程中將所有頁面都渲染好,等使用者造訪網站時,瀏覽器就直接整頁端出去享用即可。這種渲染策略常常跟以往的靜態網站(Static Website)搞混,因為他們都名為 ”Static Site”,但其實 SSG 只有在「第一頁」的時候是直接拿靜態檔案,而背景依然偷偷載入 JavaScript(也就是所謂的 Hydration),而剩下來的其他頁面還是靠 JavaScript 來主宰了!
上述的兩個方法:SSR 以及 SSG 他們都具備載入「第一頁」的 HTML 而解決了 SEO 的痛點,同時又有 SPA 那種換頁的絲滑感,況且靜態網站還可以具備分布在世界各地的 CDN 做快取,載入速度之快而堪稱完美的策略。 儘管如此,SSG 由於其 “Static Site” 的關係,也因此吃到了靜態網站的缺點:不適合資料經常變動的網站。SSG 在整個「預先渲染Pre-rendering」 的過程,是在主機上透過打包工具來產生,也就是說網頁有幾頁,就必須全部跑一遍,如果你只改了部落格的一個錯字,這對於 SSG 來說,成本實在太高了。相比 SSG,SSR 在伺服器端的渲染時機就特別適合這類場景,因此「資料是否經常變動」的網站型態決定你必須採用哪個技術來達到最高效率同時保有最低成本的作法。
說了那麼多,SSG 到底可不可以動態產生頁面?有!可以!
又被稱為 “ISR” ,這個算是為了解決 SSG 在動態資料所遇到的瓶頸。想像一下這個場景,你的部落格有200篇文章,而你突然只想修改某一篇文章的某一個字,每次修改存檔就要跑一遍 200 篇文章的渲染工作,實在是太費力了,根本本末倒置。因此, ISR 所提供的解決方案,就是在該頁面設定一個 ”定時器“,(又被稱為 revalidate time)經過一定時間後,確認後端伺服器的資料是否有變動,若有,則命令伺服器「直接重新製作」,但是伺服器只會製作有變動的那一頁,而終於不用再重新跑 200 遍了。
Ok,到此為止,那些新潮看似完美的渲染機制如 SSR, SSG 或是 ISR 都一度成為了主流,他們都擁有了一個共同點可以讓他們達成目的:Hydration,也就是只讓第一頁載入完整的 HTML 。但是當 JavaSript 檔案過於龐大的時候,這種 Hydration 的過程就會非常久,導致初始化頁面的 HTML 沒有動靜。對,沒有動靜!因為 JavaScript 還沒載入完畢,因此頁面根本沒有作用,也無法跳轉,這個大大拉長了其中一個評估網頁表現的時間因素:Time to Interactive(TTI).
所以,JavaScript 太大包該怎麼辦?是不是感覺問題又回到了 SPA 當初遇到的困難?JavaScript 太大包,那就切!切!切!
Streaming SSR 算是 SSR 的一種優化方式。在傳統 SSR的過程中,瀏覽器必須要等到伺服器整個渲染完第一頁之後,才能開始發動 Hydration(也就是載入 JavaScript;而 Streaming SSR 就是讓這件事「分段」,把渲染第一頁的工作切成更小的片段,使得瀏覽器可以在伺服器邊做 SSR 的過程中同步 Hydration,加速了第一頁的載入速度。
此種方法又稱為 Partial Hydration. 我第一次看到這個名詞,是來自於 Astro 框架的 Island Architecture. Astro 主張網頁以靜態檔案為主,而所有需要互動的 JavaScript 元件則是透過他們家的 “Island” 技術封裝起來,並在頁面進入瀏覽器時僅載入所需要的互動元件。由於 JavaScript 是以 Island 為單位來存在,因此在網頁 Hydration 的過程中是分段且平行載入的,大大解決了一大包 JavaScript 在 Hydration 過程中緩慢的問題,縮短了上述所提到 Time to Interactive(TTI) 的時間。
React Server Component 簡稱為 RSC,這當初的概念是由 React 所提出:”Zero-bundle-size React Server Component”. 。在 Streaming SSR 解決了傳統 SSR 所遇到的問題後,React 基於這種態勢繼續發展下去。在 React 誕生時的招牌 "Component" 原先是在 SPA 裡解決程式碼複用的問題,而這一切的功能原本都只發生在 Client Side, 直到 React v18 後誕生的 React Server Component,讓原本只存在 Client Side 的 Component 也能在 Server Side 被執行。想像一下原本 SSR 中 Server 要負責渲染一整頁的 HTML 的範圍縮小到 "Component" 的程度:一個頁面中的元件 (Component),需要資料庫上的資料才能渲染的部分會變成 Server Component(例如:文章標題或內文),而不需要資料就可以先被渲染的部分就會變成 Client Component (例如:按鈕或搜尋列)。透過這樣的方式來做職責區分,而被歸類在 Server Component 的那些片段,則會從需要 Hydration 的那一包 JavaScirpt 中剔除,進而減少整包的重量,加速 Hydration 的速度!所以說,所謂 "Zero-bundle-size React Server Component" 就是宣告該 Component 片段程式碼會從 Hydration 那一包中抽離並放進 Server 端囉!
Resumability 是來自於這兩年新興框架 Qwik 的全新概念。他們認為 Hydration 就是負擔太重,太慢了!一般來說,網站內容會先被使用者看到,然後背景發生 Hydration,最後網站才能開始互動,但 Qwik 的 Resumability 選擇拋棄 Hydration,使用其核心引擎 Optimizer,讓將程式碼切分成眾多的 Event Listener 綁定在 HTML 節點上面,讓使用者看到內容後可以直接互動,不用再等 Hydration!完整影片可以看這邊:Qwik: Under-The-Hood of a Resumable JavaScript Framework ,以及看更多 Resumable 和 Hydration 的比較:Resumable vs. Hydration
以上這些技巧,你必須先了解,最新不代表最好!但是,透過了解各種不同的演算方法和渲染組合拳,可以找到最適合你網站類別的渲染策略,目標將渲染速度加到最高,成本降到最低,達到網頁最佳化的結果。以上的 11 種方法是目前我在前端世界裡走跳多年所遇見的渲染策略和組合技,歡迎私信我,交流更多前端知識。