最近,我們將 Universe.com 主頁的性能提升了十倍以上。在本文中,我們將解析實現這一重大改進的具體技術手段。
但在開始之前,讓我們先對網絡性能的重要意義進行一番論證(本文末尾提供相關案例研究鏈接):
在本篇文章中,我們將簡要介紹以下幾大有助于我們提高頁面性能的主要領域:
這里再介紹一點我們的情況:我們的主頁由 React(TypeScript)、Phoenix(Elixir)、Puppeteer(headless Chrome)以及 GraphQL API(Ruby on Rails)構建而成。以下為主頁在移動設備上顯示的效果:
Universe 主頁與瀏覽效果
性能測量
沒有數據作為支持,一切意見都將毫無意義。
—— W. Edwards Deming
實驗室工具
實驗室工具能夠立足受控環境從預定義的設備及網絡設置中收集數據。利用這些工具,我們能夠輕松調試任何性能問題并實現良好的可重復測試。
Lighthouse 就是一款立足本地計算機對 Chrome 內網頁進行審計的出色工具。其能夠提供一系列關于如何提高性能、可訪問性以及搜索引擎優化的實用性提示。下面,我們來看模擬高速 3G 加 4x CPU 場景下的 Lighthouse 性能審計報告:
之前與之后:首屏內容填充(簡稱 FCP)性能實現 10 倍提升
然而,單純使用實驗室工具也會帶來不少弊端:這類工具不一定能準確反映出最終用戶所面臨的設備、網絡、位置以及多種其它現實因素造成的性能瓶頸。正因為如此,我們才需要配合現場工具進行補充。
現場工具
現場工具允許我們模擬并測量用戶的真實頁面負載。目前有多種服務可幫助大家從實際設備當中獲取真實性能數據:
WebPageTest 報告
渲染
內容的渲染可通過多種方法實現,其中每一種都擁有獨特的優勢與缺點:
客戶端渲染
以前,我們將自己的主頁與 Ember.js 框架一同實現為采用客戶端渲染方法的單頁面應用。但這種做法的一大問題在于,我們的 Ember.js 應用程序包過大。這意味著在瀏覽器下載 JavaScript 文件并對其進行解析、編譯與執行的過程中,用戶只能對著空白屏幕發呆:
最要命的空白屏幕
因此,我們決定利用 React 重構應用當中的某些部分。
預渲染與服務器端渲染
客戶端渲染應用程序的具體構建——例如采用 React Router DOM,仍然會帶來與 Ember.js 相同的問題。JavaScript 需要占用大量資源,而且訪問者需要經歷一段首屏內容填充周期才能看到實際內容。
因此在決定使用 React 之后,我們開始嘗試其它潛在的渲染選項,以確保瀏覽器能夠更快地完成內容渲染。
使用 React 時的常規渲染選項
因此,我們打算嘗試一下混合方法,即發揮每一種渲染選項中的獨特優勢。
運行時預渲染
Puppeteer 是一套 Node.js 庫,允許用戶使用 headless Chrome。我們希望嘗試利用 Puppeteer 在運行時當中實現預渲染。這代表著一種有趣的混合方法:利用 Puppeteer 進行服務器端渲染,同時利用 hydration 進行客戶端渲染。感興趣的朋友可以點擊此處查看谷歌提供的關于如何利用 headless 瀏覽器進行服務器端渲染的相關提示。
利用 Puppeteer 對 React 應用程序進行運行時預渲染
這種方法具備以下優勢:
但在采用這種方法的過程中,我們也遇到了一些挑戰:
利用 Puppeteer 的服務器端渲染架構
? 穩定性。對眾多 headless 瀏覽器進行規模伸縮,同時保持進程不致過熱并實現負載均衡絕對是一項高難挑戰。我們嘗試了不同的托管方法,包括在 Kubernetes 集群內進行自托管,以及利用 AWS Lambda 與 Google Cloud Functions 實現無服務器計算。我們注意到,后一種方法在配合 Puppeteer 時存在一些性能問題:
AWS Lambdas和GCP函數的Puppeteer響應時間
在配合 AWS Lambdas 與 GCP Functions 時,Puppeteer 的響應時間結果隨著我們對 Puppeteer 熟悉程度的逐步提升,我們開始對初始方法進行迭代(后文將具體說明)。我們還進行了其他一系列有趣的實驗,希望通過 headless 瀏覽器渲染 PDF。再有,即使不編寫任何代碼,我們也能夠利用 Puppeteer 自動進行端到端測試。而且除了 Chrome 之外,Puppeteer 現在還支持 Firefox 瀏覽器。
混合渲染方法
在運行時中使用 Puppeteer 并非易事。正因為如此,我們才決定在構建時中加以使用,同時配合一款工具用于在運行時內從服務器端獲取用戶生成的實際內容。很明顯,這款工具必須擁有比 Puppeteer 更強大的穩定性與吞吐能力。
我們決定使用 Elixir 編程語言。Elixir 看起來與 Ruby 非常相似,但運行在 BEAM(Erlang VM)之上。順帶一提,BEAM 專門為構建高容錯、高穩定性系統而生。
Elixir 采用 Actor 并發模型。每個“Actor”(即 Elixir 進程)的內存占用量都非常有限,僅為 1 到 2 KB。這意味著系統將能夠同時運行成千上萬個獨立的進程。Phoenix 則是一套 Elixir Web 框架,能夠支持高吞吐量,并允許開發者在各個獨立的 Exlixir 進程當中處理各項 HTTP 請求。
我們將上述方法結合起來,充分利用其各自優勢,希望能夠切實滿足自身需求:
Puppeteer 用于實現預渲染,Phoenix 則用于實現服務器端渲染
我們可以繼續構建一款簡單的瀏覽器 React 應用程序,并在無需等待最終用戶設備 JavaScript 處理過程的同時獲得快速初始頁面加載效果。
利用 Puppeteer 建立預渲染架構,利用 Phoenix 進行服務器端渲染,React 則在客戶端上實現 hydration
網絡
內容交付網絡 (CDN)
利用 CDN 可幫助我們實現內容緩存,并加速其在全球范圍內的交付速度。我們選擇了 Fastly.com,其目前處理著全球超過 10% 的請求總量,并得到 GitHub、Stripe、Airbnb 以及 Twitter 等諸多廠商的青睞。
Fastly 允許我們編寫定制化緩存,并可利用 VCL 配置語言建立路由邏輯。下面,我們將具體聊聊基礎請求流如何根據路由、請求頭等因素分步起效:
VCL 請求流
提高性能的另一個選項是配合 Fastly 在邊緣位置使用 WebAssembly(WASM)。大家可以將其視為一種無服務器模式,只是處于邊緣位置;所使用的語言則包括 C、Rust、Go 以及 TypeScript 等等。Cloudflare 就擁有一個類似的項目,用于在 Workers 上支持 WASM。
緩存
盡可能多地利用緩存處理請求是改善性能水平的關鍵所在。立足 CDN 層級進行緩存,將能夠更快地為新用戶提供響應。而通過發送 Cache-Control 頭進行緩存,則可加快瀏覽器中重復請求的響應速度。
大多數構建工具(例如 Webpack)允許用戶向文件名當中添加哈希值。由于指向這些文件的任何變更都會產生新的輸出文件名,因此大家可以安心將文件添加至緩存當中。
通過 HTTP/2 進行文件緩存與編碼
GraphQL 緩存
發送 GraphQL 請求的一種常見方法,就是利用 POST HTTP 方法。而我們選擇了立足 Fastly 層級對部分 GraphQL 請求進行緩存:
利用一條 SHA256 URL 參數發送 POST GraphQL 請求
以下是其它一些值得參考的潛在 GraphQL 緩存策略:
編碼
目前,所有主流瀏覽器都支持利用 gzip 加 Content-Encoding 標頭進行數據壓縮。這意味著面向瀏覽器的發送數據量更低,從而帶來更快的內容傳遞速度。此外,如果瀏覽器支持,大家也可以嘗試使用效率更高的 brotli 壓縮算法。
HTTP/2 協議
HTTP/2 是 HTTP 網絡協議的新版本(DevConsole 中簡稱為 h2)。由于存在著以下幾項與 HTTP/1.x 版本間的顯著差別,切換至 HTTP/2 能夠帶來性能提升:
HTTP/2 Server Push
由于給現有工具及生態系統(例如 rack)引入了一系列顛覆性的變更,很多編程語言與庫并不能完全支持 HTTP/2 的全部功能。但即便如此,我們仍然可以在部分合適的場景中使用 HTTP/2。舉例來說:
HTTP/2 推送字體
對 JavaScript 以及 CSS 的推送功能同樣非常實用。但請注意不要過度推送,您可點擊此處了解一些相關問題:https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/
瀏覽器中的 JavaScript
包大小預算
JavaScript 性能優化中的頭號規則就是,不要使用 JavaScript。
—— 我自己
如果您已經擁有現成的 JavaScript 應用程序,那么設置預算規則能夠提高包大小的可見性,同時確保全部內容都可容納于同一頁面當中。超出預算后,開發人員則需要謹慎考慮并盡量防止規模進一步增長。以下是預算設置方面的相關示例:
您可以使用 bundlesize 工具包或者 Webpack 性能提示與限制進行預算跟蹤:
Webpack 性能提示與限制
消除依賴性
Sidekiq 曾在一篇博文中提到:“代碼越少,運行速度越快。代碼越少,bug 就越少。代碼越少,占用的內存量就越低。代碼越少,理解起來就越輕松。”
遺憾的是,實際 JavaScript 場景中往往存在著不計其數的依賴關系。您可以試試:ls node_modules | wc -l。
在某些情況下,添加依賴性是一種必然的選擇。在這種情況下,依賴性的包大小應該被視為決定您實際工具包選擇的重要依據。我強烈建議大家使用 BundlePhobia:
BundlePhobia 能夠提示將 npm 工具包添加至您數據包中帶來的實際成本
代碼拆分
使用代碼拆分是另一種能夠顯著提高 JavaScript 性能的好辦法。其本質在于分解代碼片段并僅向用戶交付當前所需要的部分。以下是關于代碼拆分的相關示例:
您可以利用 Webpack 動態導入以及 React.lazy 配合 Suspense 實現代碼拆分。
利用動態導入以及 React.lazy 配合 Suspense 實現代碼拆分。
相較于默認導出,我們構建的函數可取代 React.lazy 以支持點名導出。
Async 與 defer 腳本
目前,全部主流瀏覽器皆在 script 標簽上支持 async 與 defer 屬性:
加載JavaScript的不同方式
幾種不同的 JavaScript 加載方式:
下面來看 head 標簽下不同腳本間的可視化差異:
幾種不同的腳本抓取與執行方式
圖像優化
雖然與 100 KB 的圖像相比,100 KB 的 JavaScript 代碼明確會帶來更高的性能成本,但我們同樣有必要重視對圖像內容的優化調整。
削減圖像大小的有效手段之一,是在適用的瀏覽器當中采用更加輕量化的 WebP 圖像。對于那些無法支持 WebP 的瀏覽器,大家則可以采取以下幾種策略:
WebP 圖像
僅當圖像位于視圖當中或者附近時才進行內容加載,堪稱多圖像初始頁面加載過程中效果最顯著的提速手段之一。您可以在受支持的瀏覽器當中使用 IntersectionObserver 功能,也可以利用其它一些替代性工具實現相同的結果——例如 react-lazyload。
在滾動過程中進行圖像的延遲加載
其它一些圖像優化策略還包括:
常規圖像與漸進圖像之間的加載效果差異
大家也可以考慮使用通用型 CDN 或者圖像專用 CDN,其通常會直接提供與圖像相關的優化功能。
資源提示
資源提示(Resource hints) 允許我們優化資源交付、降低往返次數,同時獲取資源以實現頁面瀏覽過程中的內容交付提速。
帶有 link 標簽的資源提示
提前進行預連接以避免 DNS、TCP 以及 TLS 往返延遲
當然,prerender 以及 dns-prefetch 等其它一些資源提示同樣非常重要。其中一部分資源提示可在響應標頭中進行指定。需要提醒大家的是,請務必小心使用資源提示。一旦開始濫用,您的頁面中可能包含大量不必要的請求并快速下載過量數據,這種情況顯然不利于使用蜂窩數據的移動用戶。
總結
應用程序的性能改善之路代表著一個永遠盡頭的過程,且通常要求我們在整個堆棧當中持續作出更改。
每次看到下面這段視頻,我總會想起你們努力減少應用包大小的樣子。
——我的同事
馬上把一切不需要的東西從飛機上扔下去!——電影《珍珠港》
以下列出了我們已經使用或者計劃嘗試的其它一些潛在性能改進思路:
另外還有更多令人興奮的想法可供嘗試。希望本文提出的信息及以下案例研究能夠激發出大家改善應用程序性能的更多靈感:
來源:前端之巔
作者:exAspArk
譯者:核子可樂
原文:https://engineering.universe.com/improving-browser-performance-10x-f9551927dcff?gi=ef65642ac481
當今互聯網時代,網頁作為企業展示產品和服務的窗口,其在搜索引擎中的排名顯得尤為重要。而要提高網頁在搜索引擎結果中的排名,一個重要的環節是對網頁的代碼進行優化。本文將詳細探討SEO優化中的網頁代碼優化技巧與方法,旨在幫助網頁開發者更好地理解和應用這些技巧,提升網頁的搜索引擎可見度和排名。
要進行網頁代碼的優化,我們需要了解搜索引擎的工作原理。搜索引擎通過自動化程序(爬蟲)來抓取網頁內容,然后對這些內容進行分析和索引。因此,良好的網頁代碼結構和標準化的標記語言對搜索引擎來說具有重要意義。
一種常用的網頁標記語言是HTML(超文本標記語言)。在進行網頁代碼優化時,我們可以從以下幾個方面入手。首先,合理利用標題標簽。搜索引擎通過標題標簽來判斷網頁的主題和重要性。合理使用H1-H6標簽,將網頁內容劃分成有層次感和重點的部分,能夠提高網頁的可讀性和搜索引擎的收錄率。其次,合理使用關鍵詞。關鍵詞是用戶在搜索引擎中輸入的詞語,搜索引擎通過匹配關鍵詞和網頁內容來決定網頁的排名。因此,在網頁的標題、正文、鏈接和圖片等地方合理使用關鍵詞,能夠提高網頁在搜索引擎結果中的排名。
除了HTML,CSS(層疊樣式表)也是網頁代碼優化的一個關鍵因素。CSS可以使網頁的樣式和結構相分離,這樣搜索引擎爬蟲在抓取網頁內容時可以更加方便地理解網頁的結構。在進行CSS代碼優化時,我們可以從以下幾個方面入手。首先,盡量減少CSS文件的數量和大小。過多的CSS文件和過大的文件容量會影響網頁的加載速度,從而影響用戶體驗,也會影響搜索引擎對網頁的收錄和排名。其次,避免使用內嵌CSS。內嵌CSS會使網頁的代碼冗余,增加搜索引擎分析的難度。非常后,合理使用選擇器和屬性。選擇器用于選擇要應用樣式的HTML元素,而屬性用于設置該元素的樣式。合理使用選擇器和屬性可以簡化CSS代碼,提高代碼的可讀性和維護性。
還有一些其他的網頁代碼優化技巧和方法可以提高網頁在搜索引擎結果中的排名。首先,優化網頁的URL。URL是網頁的地址,搜索引擎通過URL來判斷網頁的主題和重要性。合理使用關鍵詞和簡潔的URL結構能夠提高網頁的可讀性和搜索引擎的收錄率。其次,加快網頁的加載速度。網頁加載速度是搜索引擎排名的重要因素之一,用戶也對網頁的加載速度非常敏感。通過對網頁代碼和服務器進行優化,如減少HTTP請求、使用壓縮和緩存等技術手段,可以提高網頁的加載速度,從而提高用戶體驗和搜索引擎的排名。非常后,優化網頁的移動端顯示。在移動互聯網時代,越來越多的用戶通過移動設備訪問網頁。因此,優化網頁在移動設備上的顯示效果也變得非常重要。合理使用響應式設計、優化圖片和減少不必要的內容等方法可以提高網頁在移動端的排名和用戶體驗。
網頁代碼優化是提高網頁在搜索引擎結果中排名的重要環節。通過合理利用HTML和CSS、優化網頁的URL、加快網頁的加載速度、優化網頁的移動端顯示等方法,可以提高網頁的搜索引擎可見度和排名。希望本文可以幫助網頁開發者更好地理解和應用這些優化技巧,從而提升網頁的競爭力和影響力。
該文章由上海集錦科技(上海網站建設 http://www.jijinweb.com)原創編寫。
要性報告
編輯
*請認真填寫需求信息,我們會在24小時內與您取得聯系。