寫在前面
SSR(Server-Side Rendering)並不是什麼新奇的概唸,前後端分層之前很長的一段時間裡都是以服務端渲染為主(JSP、PHP),在服務端生成完整的 HTML 頁面
(摘自 前端渲染模式的探索)
也就是說,歷經 SSR 到 CSR 的大變革之後,如今又從 CSR 出發去探索 SSR 的可能性……似乎兜兜轉轉又回到了起點,在這之間發生了什麼?如今的 SSR 與當年的 JSP、PHP 又有什麼區別?
一。SSR 大行其道
回到論壇、博客、聊天室仍舊火熱的年代,行業最佳實踐是基於 JSP、PHP、ASP/ASP.NET 的動態網站
以 PHP 為例:
<?php if ( count( $_POST ) ): ?>
<?php include WTG_INCPATH . '/wechat_item_template.php' ?>
<div style="...">
<div id="wechat-post" class="wechat-post" style="...">
<div class="item" id="item-list">
<?php
$order = 1;
foreach ( $_POST['posts'] as $wechat_item_id ) {
echo generate_item_list( $wechat_item_id, $order );
$order++;
}
?>
</div>
<?php
$order = 1;
foreach ( $_POST['posts'] as $wechat_item_id ) {
echo generate_item_html( $wechat_item_id, $order );
$order++;
}
?>
<fieldset style="...">
<section style="...">
<p style="...">如果心中仍有疑問,請查看原文並留下評論噢。(<span style="font-size:0.8em; font-weight:600">特別要緊的問題,可以直接微信聯繫 ayqywx</span> )</p>
</section>
</fieldset>
</div>
<script>
function refineStyle () {
var post = document.getElementById('wechat-post');
// ul ol li
var uls = post.getElementsByTagName('ul');
for (var i = uls.length - 1; i >= 0; i--) {
uls[i].style.cssText = 'padding: 0; margin-left: 1.8em; margin-bottom: 1em; margin-top: -1em; list-style-type: disc;';
uls[i].removeAttribute('class');
};
}
document.addEventListener('DOMContentLoaded', function() {
refineStyle();
});
</script>
</div>
<?php endif ?>
(摘自 ayqy/wechat_subscribers,一款用來 自動生成微信公眾平台圖���消息 的 WordPress 插件)
這一時期網頁內容完全由服務端渲染,客戶端(瀏覽器)接收到的是融合了服務數據的 HTML,以及少量內聯的(表單)交互邏輯和樣式規則,支撐著早期大量動態網站的正是這種純 SSR 模式
但隨著技術實踐的深入,這種模式逐漸暴露出了一些問題:
-
性能差:每一個請求過來都要重新執行一遍數據邏輯和視圖邏輯,動態生成 HTML,即便其中很大一部分內容是相同的
-
機器成本高:Tomcat/Apache 等應用服務器的並發處理能力遠不及 [nginx](/articles/nginx-https 反向代理/) 之類的 Web 服務器,因此需要部署更多的機器
-
開發/維護難:前後端代碼摻雜在一起,人員協作是個問題,並且修改維護要十分謹慎(標籤結構容易被破壞)
面對這些問題,兩個思路逐漸變得清晰起來,動靜分離與前後端分層,前者解決性能和機器成本的問題,後者解決開發/維護的問題
二。動靜分離
為了充分利用 Web 服務器的靜態資源處理優勢,同時減輕應用服務器的負擔,將資源分為兩類:
-
靜態資源:圖片、CSS、JS 等公用的,與具體用戶無關的資源
-
動態資源:應用邏輯、數據操作等與具體用戶密切相關的資源
兩種資源分開部署,把靜態資源部署至 Web 服務器或 CDN,應用服務器只部署動態資源。如此這般,靜態資源響應更快了(瀏覽器緩存、CDN 加速),應用服務器壓力更小了,皆大歡喜
然而,視圖邏輯卻被我們漏掉了,HTML 算作靜態資源還是動態資源?
前後端分層就是為了回答這個問題
三。前後端分層
視圖邏輯的特殊之處在於:
-
與數據密切相關
-
服務端與客戶端均可承載視圖邏輯
也就是說,HTML 視圖結構的創建和維護工作,可以由服務端完成,也可以在客戶端完成,都依賴服務數據。但與服務端相比,客戶端環境有一些優勢:
-
無需刷新(重新請求頁面)即可更新視圖
-
免費的計算資源
因此,視圖邏輯劃分到了客戶端(即 CSR),以數據接口為界,分成前後端兩層:
-
後端:提供數據及數據操作支持
-
前端:負責數據的呈現和交互功能
自此,前後端各司其職,前端致力於用戶體驗的提升,後端專注業務領域,並行迭代,(不涉及接口變化時)互不影響
四。CSR 如日中天
前後端分層之後,進入了 CSR 的黃金時代,探索出了功能插件、UI 庫、框架、組件等多種代碼複用方案,最終形成了繁榮的組件生態
組件化的開發方式之下,純 CSR 模式日益盛行:
<!DOCTYPE html>
<html>
<head>
<title>My Awesome Web App</title>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
這種模式下,幾乎所有的頁面內容都由客戶端動態渲染而來,包括創建視圖、請求數據、融合數據與模版、交互功能在內的所有工作,都交由一套數據驅動的組件渲染機制來全權管理,而不必再關注組件之下的 DOM 結構維護等工作,有效提高了前端的生產效率。但一些問題也隨之而來:
-
在組件樹首次渲染完之前,頁面上無法展示任何內容,包括 loading
-
數據請求必須等到所屬組件開始渲染才能發出去
這些問題的根源在於目前的組件渲染流程是同步阻塞的,對首屏性能提出了挑戰:
-
低端設備上 JS 執行效率低,白屏時間長
-
弱網環境下數據返回慢,loading 時間長
CSR 雖然利用了用戶設備的計算資源,但同時也受其性能、網絡環境等不可控因素的制約。於是,大家又重新將目光聚集到了 SSR
五。SSR 東山再起
SSR 模式下,首屏內容在服務端生成,客戶端收到響應 HTML 後能夠直接呈現內容,而無需等到組件樹渲染完畢
雖然核心思想都是在服務端完成頁面渲染工作,但如今的 SSR 與先前大不相同,體現在:
-
出發點:為了更快、更穩定地呈現出首屏內容
-
成熟度:建立在前端成熟的組件體系、模塊生態之上,基於 Node.js 的同構方案成為最佳實踐
-
獨立性:仍然保持著前後端分層,不與業務領域的應用服務強耦合
也就是說,如今的 SSR 是為了解決前端層的問題,結合 CSR 優化內容加載體驗,是在 CSR 多年積澱之上的擴展,與現有的前端技術生態保持著良好的相容性。而當年的 SSR 更多地是為了實現功能,解決溫飽問題
再看當年 SSR 面臨的幾個問題:
-
性能差:每一個請求過來都要重新執行一遍數據邏輯和視圖邏輯,動態生成 HTML,即便其中很大一部分內容是相同的
-
機器成本高:Tomcat/Apache 等應用服務器的並發處理能力遠不及 [nginx](/articles/nginx-https 反向代理/) 之類的 Web 服務器,因此需要部署更多的機器
-
開發/維護難:前後端代碼摻雜在一起,人員協作是個問題,並且修改維護要十分謹慎(標籤結構容易被破壞)
引入 SSR 之後這些問題捲土重來,但這些年的技術發展為解決這些問題提供了新的思路:
-
實時渲染的性能問題:動靜分離的思路仍然適用,例如 Static Generation
-
服務器資源成本問題:雲計算的發展有望大幅降低機器成本,例如 Node FaaS
-
SSR 部分與 CSR 部分的開發/維護問題:同構為解決開發/維護難題提供了一種新思路(之前的思路是前後端分層,但這一次分不開了),維護同一份代碼,跑在不同的運行環境輸出不同形式的目標產物
其中,Static Generation(也叫 SSG,Static Site Generation)是指在編譯時生成靜態 HTML(可部署至 CDN),避免實時渲染的性能開銷:
Static Generation (Recommended): The HTML is generated at build time and will be reused on each request.
但並非所有頁面都能在編譯時靜態生成,一種可行的實踐方案是將 SSR 與 Static Generation 結合起來,只對內容依賴個性化數據、或者頻繁更新的頁面走 SSR,其餘場景都走 Static Generation:
You should ask yourself: "Can I pre-render this page ahead of a user's request?" If the answer is yes, then you should choose Static Generation.
至此,沉寂多年的 SSR 又煥發出了新的活力
暫無評論,快來發表你的看法吧