也許你想要的只是一個靜態應用

blank

也許你想要的只是一個靜態應用

前言

記得剛開始學習做網站的時候,最先學會寫出一個 HTML,再給他加上一些 CSS 樣式作為修飾,也許最後再加上一丟丟 Javascript 去輔助出一些效果,duang ~ 一個最最基礎的網站就完成了,然後我們把它丟到伺服器上託管著,使用者就可以訪問到了。 再此之後,我們又學會了用 JAva、Python 做動態網站,學會用更複雜的 Javascript 去開發前端 MVC 應用、前後端分離、RESTful。。。

然鵝,最近幾年靜態網站似乎又不可思議的火了起來,類似於 Gatsby.js、Next.js 等帶 SSG(Static Site Generator) 功能框架受到了一些人的歡迎,這是科技的退步,還是人心的復古,讓我們一起走進今天的話題——也許你想要的只是一個靜態網站。

關於 SSG、JAMstack 究竟是什麼,這裡就不做贅述了。 這裡給出一個我認為最簡短的解釋:

  • SSG - 產生靜態網頁的框架,基於你熟知的技術(React,Vue)
  • JAMStack - 由 JAVAScript, APIs, Markup 構成的網站應用,Markup 這裡指預渲染好的靜態檔。

既然是預渲染好的,那麼帶來的問題也很明顯,很多人擔心靜態網站不夠"動態",在業務過於"動態"化的今天,可能無法正常使用,比如業務需要頻繁操作資料庫,需要頻繁使用者交互等。 而我在實際開發過幾個 Gatsby 和 Next 項目之後,得出的結論是 SSG 其實可以做到最大程度的"靜態化",或者說,他可以在很多場景發揮出自己應有的作用,甚至比你想像的還多。 也許在未來「靜態驅動」型應用會佔據前端開發的一片江山甚至有可能成為主流。

那麼,何為"靜態"

用傳統「靜態」網站的概念來形容現代的 Static Site 總覺得有點不太合適,那讓我們從新認識一下現代前端技術中的 Static Site:Web 應用以數據裝載完成且渲染好的 HTML 檔構成為基礎,配合 CSS 和 Javascript 的豐富能力將結果直接呈現給使用者,而不是在使用者請求時由服務動態生成頁面檔。

舉個例子,你可以直接將靜態應用放到 CDN 上,當使用者請求網站,服務直接將以渲染完成的 HTML 返回,並不需要任何服務端的計算,也不會給服務造成過大的壓力,服務的角色只是簡單的託管。

靜態的價值

首先我們來看看它的優勢。 當我們談論靜態應用,靜態網站,極致的性能肯定是第一值得認可的地方,原因很好解釋,服務端幾乎為零的計算量,瞬間返回結果。 除此之外,好處還很多:

穩定性

當你引入一項技術、一個工具,也順便引入了一份風險,因為無論是資料庫還是伺服器,都有可能發生故障,更有可能因為流量的激增而堵塞,當服務因壓力而掛起,使用者只會白屏等待或者訪問失敗。 這對於一個永不宕機的應用來說是災難的,而從加強服務性能本身入手解決問題,無疑將是巨大的成本,而且真正遇到這種流量海嘯的機率卻是微乎其微,所以日常就是看著 CPU 使用率不到 1% 而花著冤枉錢。 這種現象,我們俗稱為"不夠彈性",當然你可以用雲端彈性計算,Serverless 等方法來解決問題,但這無疑又增加了複雜度,可能又會帶來新的問題。 當你夜不能寐,也許會想起,我的服務還好嗎。

但如果使用的是靜態應用,問題就不存在了,無論流量如何變化,服務的計算量都是零;無論資料庫是否正常工作,頁面已經生成了,不會對線上造成任何影響;無論任何時間地點,CDN 邊緣計算都可以用最快速度在離用戶端最近的距離(使用者本地緩存除外)把頁面交付給使用者。 絕對可以讓你睡個好覺。

SEO

相對於客戶端渲染的 SPA 應用而言,靜態應用天生支援 SEO;比起服務端渲染,無需即時計算生成頁面,更直觀有效;比起 prerending 方案,更穩定可靠。 而且無論什麼爬蟲,即使技術最原始的爬蟲也能保證爬到網站訊息,因為靜態應用提供的本身就是 HTML,而且每個頁面的 SEO 訊息都是預先可見的,非常直觀且易於管理。

性能

極致的快,得益於服務的無計算過程,也得益於資源可以非常方便的緩存。 如果你把應用放到 nginx 託管,可以使用 nginx 快取;如果放到 CDN,可以直接緩存在雲端邊緣。 除此之外頁面還會緩存在瀏覽器中,而這一切幾乎不用寫一行代碼就可以做到。 或者你也可以再寫一點 PWA 來管理瀏覽器端緩存,一切都會變得更美妙。

"動態"的數據源

上面的優點看起來還不錯,但你一定有了疑問:數據是預取的,內容都已經寫死了,如果業務有些許"動態"的數據,這下應用該怎麼辦? 別著急,我們慢慢解釋。

無論數據來源是資料庫、CMS、還是檔。 我們的應用始終依賴該數據之上,我們將數據源統一抽象為原子數據,以取出某個原子數據為例,在傳統的服務端渲染應用中,當使用者請求頁面,會發生以下事件:

  1. 用戶端發出請求 /XXX ,服務端接到請求,作出反應
  2. 根據請求訊息查詢資料庫或者 CMS 找到結果
  3. 服務端將找到的資料和 HTML 模版進行字串計算,最後經過一炷香的時間渲染出新的 HTML 檔
  4. 服務端返回渲染好的 HTML 檔給用戶端

那麼在靜態應用這裏的情況呢? 其實我們可以有很多種設計,最容易的實現大致是靜態生成初始頁面,頁面帶有一些 Loading 態或者用戶引導,並且在此之上通過 API 和服務端交換數據,完成頁面更新:

  1. 用戶端發出請求 /XXX ,服務端接到請求,作出反應
  2. 服務端馬上返回初始化 HTML 頁面,頁面並不包含任何動態數據
  3. 在瀏覽器解析頁面之後,頁面進入等待,JS 向服務端發出異步請求
  4. 服務端接到請求,根據請求訊息查詢資料庫或者 CMS 找到結果並返回 JSON
  5. 客戶端根據返回的 JSON 更新頁面

本質上這樣的靜態應用跟一個單頁應用並沒什麼很大的區別,在初始頁面下發之後,執行同樣的用戶端渲染邏輯,在性能上也只是比單頁應用快了一個首頁載入而已。 其實也可以理解為這是一個折中方案,一方面加速了首屏渲染速度,另一方面數據能夠即時動態獲取。

那麼,我們換一個思路,假設我們不考慮折中方案,如果一開始就拿到的是完整預渲染完的頁面呢?

換個思路

我們以往的渲染思路普遍都是前端渲染或者服務端渲染,數據實時計算並通過異步或者同步返回給請求方。 如果我們在編譯時就把數據準備好呢,將已經與渲染好的 HTML 檔直接返回用戶端,這可行嗎? 首先如果這樣做,上面的請求過程將變為:

  1. 用戶端發出請求 /XXX ,服務端接到請求,作出反應
  2. 伺服器立即將完整的 HTML 檔返回給用戶端

沒有資料庫查詢,沒有即時計算,返回一個 web 應用跟返回一個"Hello world"字串的原理一樣。 而且因為是純 HTML 檔,瀏覽器可以緩存在客戶側,一些運轉起來都是極致的快。

編譯時數據預取

上面的場景很美好,但現實很骨感。 我們想要完成預算就得完成編譯時數據預取的工作,而且是"全量"的數據,資料依然來自於 CMS,資料庫或者數據檔。 當你使用命令 datsby build 或者 next build && next export 去生成靜態應用時,編譯程式將請求所有應用依賴到的數據源,他們可以是 API,可以是連接池,可以是 JSON 檔,然後按你希望的方式拼接進字串中,然後使用 React 提供的 server-rending 相關方法生成一個個完整的 HTML 檔。

在 Gatsby 中,社區設計了豐富的外掛程式,用於請求不同類型的數據源,並且互相相容不同的頁面和元件,這裡可以瞥一眼:

在 Next.js 中並沒有像 Gatsby 這麼的外掛程式化,不過也可以在下面生命週期內自由引入:

以上過程都會自動蒐集數據,自動執行編譯,這樣很容易就可以跟 CI/CD 結合,工程化和擴充性都很棒。

真的是 所有數據?

所有數據都下載一遍,生成靜態檔得有多少個,這個想法聽起來就有點滑稽,這真的可行嗎? 如果是一個部落格網站還好說,也就是每篇文章生成一個頁面;但如果是一個電商網站,是不是要生成所有商品的頁面,假設商品數以萬計,這不是光生成頁面就得半天? 如果是一個實時監控的控制台呢......

而我認為這個問題的答案並不是非黑即白,就是說這一切技術架構都得根據真實的業務和應用規模和類型來合理使用,什麼意思呢。 部落格非常適合靜態應用,文檔也非常適合靜態應用,是因為他們本身數據不會經常變動,這個"經常"的頻率是不會每隔十分鐘都發生變化,而這個不變的數據間隔跟我們重新編譯生成靜態檔相比,是遠遠大於需要構建的時間的。 而控制台一類的中後台監控頁面呢,首先數據是即時的,秒級別或者分鐘級別的變化頻率遠遠小於我們編譯做需要的時間,所以選擇靜態應用是個非常消耗且不滿足需求的方案。 那麼問題就很明顯了,如何找到合適變化的度就成為了判斷是否選擇靜態應用方案的關鍵。

那麼究竟這個變化的閾值該如何掌握呢? 讓我們深入 SSG 技術的前沿領域,Gatsby 社區一方面努力於構建大型應用的性能提升,另一方面也正在努力於"增量構建"—— incremental buildsincremental builds?Gatsby Incremental Builds Private beta),同樣 Next.js 也有類似的 RFC RFC: Incremental Static Generation。 增量構建允許我們在每一次構建中只會重新編譯發生了改變的那部分頁面和元件,而不是全部重來一遍。 這會起到多大的作用呢? 讓我們用上面沒有提到的商場來舉例。

假設一個電商網站有一萬件商品,需要生成一萬個左右的靜態頁面,這個是各自商品介紹頁面,不包括首頁,推薦頁,用戶頁面等定製化頁面。 那麼在第一次耗時較長的構建之後(這個過程可能會比你想像的要少,以我個人的經驗來說 1000 張頁面大概耗時 500ms,根據實際內容而定所以並不一定準確),靜態應用放到 CDN 完成上線,以邊緣計算的絕對速度優勢服務於全世界的使用者,用戶驚歎與真特么的快。 然鵝這時候有個商品的廠家通過 CMS 改變了售價,於是我們需要對某件商品改價,構建機器的鉤子接到推送,啟動增量編譯,將被修改的某個商品頁面單獨重新編譯后生成 HTML 檔,因為有變化頁面非常小,這個過程甚至可以瞬間發生並結束,然後就是在 CDN 上更換該單獨頁面的靜態檔,剩下的事情也可以在瞬間發生併完成。 而代碼的變化也是同理,增量構建,增量更新,增量替換。 只要保障需要重新構建的部分在一定規模內,用戶體驗幾乎是跟動態網站是差不多的。

而在未來,而隨著增加構建、構建加速等特性的不斷升級,或許這項技術會成為靜態應用方案的一個關鍵突破點,甚至成為改變生態的勝負手。 想一想,如果既有靜態應用的優點:穩定、極致性能、可擴展、低功耗,又沒有不夠"動態"的缺點,簡直美滋滋!

目前仍然存在的限制

然鵝,因為編譯速度還不夠快,所以現階段靜態應用的依舊受限,上面我也表達出了,並不是所有應用都適合靜態化。 即時控制台類型的應用就不一定適合,但其實他也可以最大程度的"靜態化",除去數據呈現部分,控制台的骨架、Header、首屏、甚至載入態都是可以靜態化的,在此之上再加上異步請求的數據去更新頁面。 也許你覺得這樣做的意義並不大,的確如此,但是如果只是為了首屏載入或者 SEO 目的,其實你已經一定程度上達到了,剩下追求使用者體驗的目標就可以更多放到 REST API (非同步請求)的優化上了。

同理,此類型的應用也可以是 Feed 流類型的應用,比如微博推特,核心內容是不斷更新的,甚至使用者個人化數據,但是頁面骨架不會變,路由不會變,靜態應用還是可以發揮作用,加速首屏渲染速度,簡化應用模型,一定程度上減少一些服務端壓力。

總結

靜態應用以及 SSG 的技術棧給前端生態帶來一些新的解決思路,當我們熟悉了要麼 SPA,要麼 SSR 之後,發現原來還可以這麼玩,老瓶裝新酒煥發生機,這無疑給我們一陣頭腦風暴。 但不論怎麼變化,用戶端交付給用戶的東西是不變的,圍繞這一點,前端工程師或者端工程師可以發揮的空間還有很多。 而這項技術本身,隨著增量構建和構建本身的不斷優化加速、解放潛力,也許 SSG 在未來會成為一個不錯的解決方案,甚至成為主流也說不定。

What do you think?

Written by marketer

blank

成為更好的程式猿! 2020年給網頁開發人員的32條建議

blank

我最喜歡的雲 IDE 推薦!