精讀前後端渲染之爭
阿新 • • 發佈:2019-01-07
很不錯的一篇關於前端和後端渲染的文章,這裡記錄一下
英文原文連線:https://medium.freecodecamp.com/heres-why-client-side-rendering-won-46a349fadb52
原文連線:https://zhuanlan.zhihu.com/p/26366128
1. 引言
我為什麼要選這篇文章呢?十年前,幾乎所有網站都使用 ASP、Java、PHP 這類做後端渲染,但後來隨著 jQuery、Angular、React、Vue 等 JS 框架的崛起,開始轉向了前端渲染。從 2014 年起又開始流行了同構渲染,號稱是未來,集成了前後端渲染的優點,但轉眼間三年過去了,很多當時壯心滿滿的框架(Rendlr、Lazo)從先驅變成了先烈。同構到底是不是未來?自己的專案該如何選型?我想不應該只停留在追求熱門和拘泥於固定模式上,忽略了前後端渲染之“爭”的“
這篇文章分析了前端渲染的優勢,並沒有進行深入探討。我想通過它為切入口來深入探討一下。
明確三個概念:「後端渲染」指傳統的 ASP、Java 或 PHP 的渲染機制;「前端渲染」指使用 JS 來渲染頁面大部分內容,代表是現在流行的 SPA 單頁面應用;「同構渲染」指前後端共用 JS,首次渲染時使用 Node.js 來直出 HTML。一般來說同構渲染是介於前後端中的共有部分。
2. 內容概要
前端渲染的優勢:-
區域性重新整理。無需每次都進行完整頁面請求
-
懶載入。如在頁面初始時只加載可視區域內的資料,滾動後rp載入其它資料,可以通過react-lazyload
-
富互動。使用 JS 實現各種酷炫效果
-
節約伺服器成本。省電省錢,JS 支援 CDN 部署,且部署極其簡單,只需要伺服器支援靜態檔案即可
-
天生的關注分離設計。伺服器來訪問資料庫提供介面,JS 只關注資料獲取和展現
-
JS 一次學習,到處使用。可以用來開發 Web、Serve、Mobile、Desktop 型別的應用
-
服務端渲染不需要先下載一堆 js 和 css 後才能看到頁面(首屏效能)
-
SEO
-
服務端渲染不用關心瀏覽器相容性問題(隨意瀏覽器發展,這個優點逐漸消失)
-
對於電量不給力的手機或平板,減少在客戶端的電量消耗很重要
以上服務端優勢其實只有首屏效能和 SEO 兩點比較突出。但現在這兩點也慢慢變得微不足道了。React 這類支援同構的框架已經能解決這個問題,尤其是 Next.js 讓同構開發變得非常容易。還有靜態站點的渲染,但這類應用本身複雜度低,很多前端框架已經能完全囊括。
3. 精讀
本次提出獨到觀點的同學有:@流形@黃子毅@camsongTurbe Xue@楊森@淡蒼@留影@FrankFang@alcat2008@javie007@xile611@twobin 精讀由此歸納。大家對前端和後端渲染的現狀基本達成共識。即前端渲染是未來趨勢,但前端渲染遇到了首屏效能和SEO的問題。對於同構爭議最多。在此我歸納一下。
前端渲染遇到的問題
前端渲染主要面臨的問題有兩個 SEO、首屏效能。
SEO 很好理解。由於傳統的搜尋引擎只會從 HTML 中抓取資料,導致前端渲染的頁面無法被抓取。前端渲染常使用的 SPA 會把所有 JS 整體打包,無法忽視的問題就是檔案太大,導致渲染前等待很長時間。特別是網速差的時候,讓使用者等待白屏結束並非一個很好的體驗。
同構的優點
同構恰恰就是為了解決前端渲染遇到的問題才產生的,至 2014 年底伴隨著 React 的崛起而被認為是前端框架應具備的一大殺器,以至於當時很多人為了用此特性而 放棄 Angular 1 而轉向 React。然而近3年過去了,很多產品逐漸從全棧同構的理想化逐漸轉到首屏或部分同構。讓我們再一次思考同構的優點真是優點嗎?1. 有助於 SEO
-
首先確定你的應用是否都要做 SEO,如果是一個後臺應用,那麼只要首頁做一些靜態內容宣導就可以了。如果是內容型的網站,那麼可以考慮專門做一些頁面給搜尋引擎
-
時到今日,谷歌已經能夠可以在爬蟲中執行 JS 像瀏覽器一樣理解網頁內容,只需要往常一樣使用
JS 和 CSS 即可。並且儘量使用新規範,使用 pushstate 來替代以前的 hashstate。不同的搜尋引擎的爬蟲還不一樣,要做一些配置的工作,而且可能要經常關注資料,有波動那麼可能就需要更新。第二是該做 sitemap 的還得做。相信未來即使是純前端渲染的頁面,爬蟲也能很好的解析。
其實同構並沒有節省前端的開發量,只是把一部分前端程式碼拿到服務端執行。而且為了同構還要處處相容 Node.js 不同的執行環境。有額外成本,這也是後面會具體談到的。
3. 提高首屏效能
由於 SPA 打包生成的 JS 往往都比較大,會導致頁面載入後花費很長的時間來解析,也就造成了白屏問題。服務端渲染可以預先使到資料並渲染成最終 HTML 直接展示,理想情況下能避免白屏問題。在我參考過的一些產品中,很多頁面需要獲取十幾個介面的資料,單是資料獲取的時候都會花費數秒鐘,這樣全部使用同構反而會變慢。
同構並沒有想像中那麼美
1. 效能把原來放在幾百萬瀏覽器端的工作拿過來給你幾臺伺服器做,這還是花挺多計算力的。尤其是涉及到圖表類需要大量計算的場景。這方面調優,可以參考walmart的調優策略。
個性化的快取是遇到的另外一個問題。可以把每個使用者個性化資訊快取到瀏覽器,這是一個天生的分散式快取系統。我們有個資料類應用通過在瀏覽器合理設定快取,雙十一當天節省了 70% 的請求量。試想如果這些快取全部放到伺服器儲存,需要的儲存空間和計算都是很非常大。
2. 不容忽視的伺服器端和瀏覽器環境差異
前端程式碼在編寫時並沒有過多的考慮後端渲染的情景,因此各種 BOM 物件和 DOM API 都是拿來即用。這從客觀層面也增加了同構渲染的難度。我們主要遇到了以下幾個問題:
-
document 等物件找不到的問題
-
DOM 計算報錯的問題
- 前端渲染和服務端渲染內容不一致的問題
而服務端由於 js require 的 cache 機制,造成前端程式碼除了具體渲染部分都只會載入一遍。這時候 window 就得不到更新了。所以要引入一個合適的更新機制,比如把讀取改成每次用的時候再讀取。
export const isSsr = () => (
!(typeof window !== 'undefined' && window.document && window.document.createElement && window.setTimeout)
);
原因是很多 DOM 計算在 SSR 的時候是無法進行的,涉及到 DOM 計算的的內容不可能做到 SSR 和 CSR 完全一致,這種不一致可能會帶來頁面的閃動。3. 記憶體溢位
前端程式碼由於瀏覽器環境重新整理一遍記憶體重置的天然優勢,對記憶體溢位的風險並沒有考慮充分。
比如在 React 的 `componentWillMount` 裡做繫結事件就會發生記憶體溢位,因為 React 的設計是後端渲染只會執行 `componentDidMount` 之前的操作,而不會執行 `componentWillUnmount` 方法(一般解綁事件在這裡)。
4. 非同步操作
前端可以做非常複雜的請求合併和延遲處理,但為了同構,所有這些請求都在預先拿到結果才會渲染。而往往這些請求是有很多依賴條件的,很難調和。純 React 的方式會把這些資料以埋點的方式打到頁面上,前端不再發請求,但仍然再渲染一遍來比對資料。造成的結果是流程複雜,大規模使用成本高。幸運的是 Next.js 解決了這一些,後面會談到。
5. simple store(redux)
這個 store 是必須以字串形式塞到前端,所以複雜型別是無法轉義成字串的,比如function。
總的來說,同構渲染實施難度大,不夠優雅,無論在前端還是服務端,都需要額外改造。
首屏優化
再回到前端渲染遇到首屏渲染問題,除了同構就沒有其它解法了嗎?總結以下可以通過以下三步解決
1. 分拆打包
現在流行的路由庫如 react-router 對分拆打包都有很好的支援。可以按照頁面對包進行分拆,並在頁面切換時加上一些 loading 和 transition 效果。
2. 互動優化
首次渲染的問題可以用更好的互動來解決,先看下 linkedin 的渲染
有什麼感受,非常自然,開啟渲染並沒有白屏,有兩段載入動畫,第一段像是載入資源,第二段是一個載入佔位器,過去我們會用 loading 效果,但過渡性不好。近年流行 Skeleton Screen 效果。其實就是在白屏無法避免的時候,為了解決等待載入過程中白屏或者介面閃爍造成的割裂感帶來的解決方案。
3. 部分同構
部分同構可以降低成功同時利用同構的優點,如把核心的部分如選單通過同構的方式優先渲染出來。我們現在的做法就是使用同構把選單和頁面骨架渲染出來。給使用者提示資訊,減少無端的等待時間。
相信有了以上三步之後,首屏問題已經能有很大改觀。相對來說體驗提升和同構不分伯仲,而且相對來說對原來架構破壞性小,入侵性小。是我比較推崇的方案。
總結
我們贊成客戶端渲染是未來的主要方向,服務端則會專注於在資料和業務處理上的優勢。但由於日趨複雜的軟硬體環境和使用者體驗更高的追求,也不能只拘泥於完全的客戶端渲染。同構渲染看似美好,但以目前的發展程度來看,在大型專案中還不具有足夠的應用價值,但不妨礙部分使用來優化首屏效能。做同構之前 ,一定要考慮到瀏覽器和伺服器的環境差異,站在更高層面考慮。附:Next.js 體驗
Next.js 是時下非常流行的基於 React 的同構開發框架。作者之一就是大名鼎鼎的 Socket.io 的作者 Guillermo Rauch。它有以下幾個亮點特別吸引我:-
巧妙地用標準化的解決了請求的問題。同構和頁面開發類似,非同步是個大難題,非同步中難點又在介面請求。Next.js 給元件新增了 getInitialProps 方法來專門處理初始化請求,再也不用手動往頁面上塞 DATA 和呼叫 ReactDOMServer.renderToString
-
使用 styled-jsx 解決了 css-in-js 的問題。這種方案雖然不像 styled-component 那樣強大,但足夠簡單,可以說是最小的成本解決了問題
-
Fast by default。頁面預設拆分檔案方式打包,支援Prefetch頁面預載入
- 全家桶式的的解決方案。簡潔清晰的目錄結構,這一點 Redux 等框架真應該學一學。不過全家桶的方案比較適合全新專案使用,舊專案使用要評估好成本