2021 年前端性能清單(PDF、Apple Pages、MS Word)
已發表: 2022-03-10本指南得到了我們在 LogRocket 的朋友的大力支持,LogRocket 是一項結合前端性能監控、會話重放和產品分析的服務,可幫助您建立更好的客戶體驗。 LogRocket跟踪關鍵指標,包括。 DOM 完成、第一個字節的時間、第一個輸入延遲、客戶端 CPU 和內存使用情況。 立即免費試用 LogRocket。
Web 性能是一個棘手的問題,不是嗎? 我們如何真正知道我們在性能方面的立場,以及我們的性能瓶頸到底是什麼? 是昂貴的 JavaScript、緩慢的網絡字體交付、沉重的圖像還是緩慢的渲染? 我們是否對 tree-shaking、範圍提升、代碼分割以及所有帶有交叉點觀察器、漸進式水化、客戶端提示、HTTP/3、服務工作者和 - 哦我的 - 邊緣工作者的花哨的加載模式進行了足夠的優化? 而且,最重要的是,我們甚至從哪裡開始提高績效,我們如何建立長期的績效文化?
過去,性能往往只是事後才想到的。 通常推遲到項目的最後,它可以歸結為縮小、連接、資產優化以及可能對服務器的config
文件進行一些微調。 現在回想起來,事情似乎發生了很大的變化。
性能不僅僅是一個技術問題:它影響從可訪問性到可用性再到搜索引擎優化的方方面面,並且在將其納入工作流程時,必鬚根據其性能影響來製定設計決策。 性能必須不斷地被測量、監控和改進,而網絡日益複雜的問題帶來了新的挑戰,使得跟踪指標變得困難,因為數據會因設備、瀏覽器、協議、網絡類型和延遲而有很大差異( CDN、ISP、緩存、代理、防火牆、負載平衡器和服務器都在性能中發揮作用)。
所以,如果我們對提高性能時必須牢記的所有事情進行概述——從項目開始到網站的最終發布——那會是什麼樣子? 您將在下面找到2021 年(希望是公正和客觀的)前端性能檢查表— 對您可能需要考慮的問題的更新概述,以確保您的響應時間快速、用戶交互順暢且您的網站不消耗用戶的帶寬。
目錄
- 全部在單獨的頁面上
- 準備:計劃和指標
績效文化、Core Web Vitals、績效概況、CrUX、Lighthouse、FID、TTI、CLS、設備。 - 設定切合實際的目標
性能預算、性能目標、RAIL 框架、170KB/30KB 預算。 - 定義環境
選擇框架、基線性能成本、Webpack、依賴項、CDN、前端架構、CSR、SSR、CSR + SSR、靜態渲染、預渲染、PRPL 模式。 - 資產優化
Brotli、AVIF、WebP、響應式圖像、AV1、自適應媒體加載、視頻壓縮、網絡字體、谷歌字體。 - 構建優化
JavaScript 模塊、模塊/無模塊模式、tree-shaking、代碼拆分、範圍提升、Webpack、差異服務、Web Worker、WebAssembly、JavaScript 包、React、SPA、部分水合、交互導入、第 3 方、緩存。 - 交付優化
延遲加載、交叉點觀察器、延遲渲染和解碼、關鍵 CSS、流式傳輸、資源提示、佈局轉換、服務工作者。 - 網絡、HTTP/2、HTTP/3
OCSP 裝訂、EV/DV 證書、打包、IPv6、QUIC、HTTP/3。 - 測試和監控
審核工作流程、代理瀏覽器、404 頁面、GDPR cookie 同意提示、性能診斷 CSS、可訪問性。 - 快速獲勝
- 下載清單(PDF、Apple Pages、MS Word)
- 我們走吧!
(您也可以只下載清單 PDF (166 KB) 或下載可編輯的 Apple Pages 文件 (275 KB) 或 .docx 文件 (151 KB)。祝大家優化愉快!)
準備:計劃和指標
微優化對於保持性能正常運行非常有用,但牢記明確定義的目標至關重要——可衡量的目標會影響整個過程中做出的任何決策。 有幾種不同的模型,下面討論的模型非常固執——只要確保儘早設定自己的優先級即可。
- 建立績效文化。
在許多組織中,前端開發人員確切地知道常見的潛在問題是什麼以及應該使用什麼策略來解決這些問題。 然而,只要沒有對績效文化的既定認可,每個決策都會變成部門的戰場,將組織打碎成孤島。 您需要業務利益相關者的支持,並且要獲得它,您需要建立案例研究或概念證明,以證明速度(尤其是我們稍後將詳細介紹的核心 Web Vitals )收益指標和關鍵績效指標( KPI )他們關心。例如,為了使性能更加有形,您可以通過顯示轉換率和應用程序加載時間以及渲染性能之間的相關性來揭示收入性能的影響。 或者搜索機器人的爬取率(PDF,第 27-50 頁)。
如果開發/設計和業務/營銷團隊之間沒有強有力的一致性,績效將無法長期維持。 研究客戶服務和銷售團隊的常見投訴,研究高跳出率和轉化率下降的分析。 探索提高性能如何幫助緩解其中一些常見問題。 根據您正在與之交談的利益相關者群體調整論點。
在移動設備和桌面設備上運行性能實驗並衡量結果(例如,使用 Google Analytics)。 它將幫助您使用真實數據建立公司量身定制的案例研究。 此外,使用 WPO Stats 上發布的案例研究和實驗數據將有助於提高企業對性能為何重要以及它對用戶體驗和業務指標的影響的敏感性。 但是,僅說明性能很重要是不夠的——您還需要建立一些可衡量和可跟踪的目標,並隨著時間的推移觀察它們。
如何到那? 在她關於建立長期績效的演講中,艾莉森麥克奈特分享了一個關於她如何幫助在 Etsy 建立績效文化的綜合案例研究(幻燈片)。 最近,Tammy Everts 談到了小型和大型組織中高效績效團隊的習慣。
在組織中進行這些對話時,重要的是要記住,就像 UX 是一系列體驗一樣,Web 性能也是一種分佈。 正如 Karolina Szczur 指出的那樣,“期望一個數字能夠提供令人渴望的評級是一個有缺陷的假設。” 因此,績效目標需要細化、可跟踪和有形。
- 目標:比你最快的競爭對手至少快 20%。
根據心理學研究,如果你想讓用戶覺得你的網站比競爭對手的網站快,你需要至少快 20%。 研究您的主要競爭對手,收集有關他們在移動和桌面上的表現的指標,並設置有助於您超越他們的閾值。 不過,要獲得準確的結果和目標,請確保首先通過研究分析來全面了解用戶體驗。 然後,您可以模仿第 90 個百分位數的體驗進行測試。為了對競爭對手的表現有一個良好的第一印象,您可以使用 Chrome UX Report( CrUX ,一個現成的 RUM 數據集,Ilya Grigorik 的視頻介紹和 Rick Viscomi 的詳細指南),或 Treo,一個 RUM 監控工具由 Chrome 用戶體驗報告提供支持。 這些數據是從 Chrome 瀏覽器用戶那裡收集的,因此報告將是特定於 Chrome 的,但它們會為您提供相當全面的性能分佈,最重要的是 Core Web Vitals 分數,分佈在您的各種訪問者中。 請注意,新的 CrUX 數據集在每個月的第二個星期二發布。
或者,您也可以使用:
- Addy Osmani 的 Chrome 用戶體驗報告比較工具,
- 速度記分卡(還提供收入影響估算器),
- 真實用戶體驗測試比較或
- SiteSpeed CI(基於綜合測試)。
注意:如果您使用 Page Speed Insights 或 Page Speed Insights API(不,它沒有被棄用!),您可以獲得特定頁面的 CrUX 性能數據,而不僅僅是聚合。 此數據對於為“著陸頁”或“產品列表”等資產設置性能目標可能更有用。 如果您使用 CI 測試預算,如果您使用 CrUX 設置目標,則需要確保您的測試環境與 CrUX 匹配(感謝 Patrick Meenan! )。
如果您需要一些幫助來展示速度優先級背後的原因,或者您希望在性能較慢的情況下可視化轉換率衰減或跳出率增加,或者您可能需要在您的組織中倡導 RUM 解決方案,Sergey Chernyshev 構建了一個 UX 速度計算器,這是一個開源工具,可幫助您模擬數據並將其可視化以推動您的觀點。
有時您可能想更深入一點,將來自 CrUX 的數據與您已經必須快速找出減速、盲點和低效率所在的任何其他數據相結合——為您的競爭對手或您的項目。 在他的工作中,Harry Roberts 一直在使用 Site-Speed Topography 電子表格,他用它來按關鍵頁麵類型分解性能,並跟踪它們之間的不同關鍵指標。 您可以將電子表格下載為 Google 表格、Excel、OpenOffice 文檔或 CSV。
如果您想一路走下去,您可以在站點的每個頁面上運行 Lighthouse 性能審計(通過 Lightouse Parade),並將輸出保存為 CSV。 這將幫助您確定競爭對手的哪些特定頁面(或頁麵類型)表現更差或更好,以及您可能希望將精力集中在哪些方面。 (對於您自己的站點,最好將數據發送到分析端點!)。
以這種方式收集數據、設置電子表格、削減 20% 並設置您的目標(績效預算)。 現在你有一些可測量的東西來測試。 如果您牢記預算並嘗試僅發送最少的有效負載以快速進行交互,那麼您就走在了合理的道路上。
需要資源才能開始?
- Addy Osmani 寫了一篇非常詳細的文章,內容涉及如何開始性能預算、如何量化新功能的影響以及超出預算時從哪裡開始。
- Lara Hogan 關於如何使用性能預算進行設計的指南可以為設計師提供有用的指導。
- Harry Roberts 發布了關於設置 Google Sheet 的指南,以顯示第三方腳本對性能的影響,使用 Request Map,
- Jonathan Fielding 的績效預算計算器、Katie Hempenius 的 perf-budget-calculator 和 Browser Calories 可以幫助創建預算(感謝 Karolina Szczur 的提醒)。
- 在許多公司中,績效預算不應該是雄心勃勃的,而是務實的,作為避免滑過某個點的標誌。 在這種情況下,您可以選擇過去兩週內最差的數據點作為閾值,然後從那裡獲取。 績效預算,務實地向您展示了實現這一目標的策略。
- 此外,通過設置帶有圖表報告構建大小的儀表板,使性能預算和當前性能都可見。 有許多工具可以讓您實現這一目標:SiteSpeed.io 儀表板(開源)、SpeedCurve 和 Calibre 只是其中的一小部分,您可以在 perf.rocks 上找到更多工具。
一旦你有了預算,就可以將它們整合到你的構建過程中,使用 Webpack Performance Hints 和 Bundlesize、Lighthouse CI、PWMetrics 或 Sitespeed CI,以強制執行拉取請求的預算,並在 PR 評論中提供分數歷史記錄。
要將性能預算公開給整個團隊,請通過 Lightwallet 在 Lighthouse 中集成性能預算,或使用 LHCI Action 進行快速 Github Actions 集成。 如果你需要一些自定義的東西,你可以使用webpagetest-charts-api,一個端點API,從WebPagetest結果構建圖表。
不過,性能意識不應僅來自性能預算。 就像 Pinterest 一樣,您可以創建一個自定義eslint規則,該規則禁止從已知依賴重的文件和目錄導入,並且會使包膨脹。 設置可以在整個團隊中共享的“安全”包列表。
此外,考慮對您的業務最有利的關鍵客戶任務。 研究、討論和定義關鍵操作的可接受時間閾值,並建立整個組織已批准的“UX 就緒”用戶時間標記。 在許多情況下,用戶旅程將涉及許多不同部門的工作,因此在可接受的時間安排方面保持一致將有助於支持或阻止未來的性能討論。 確保增加的資源和功能的額外成本是可見和理解的。
將績效工作與其他技術計劃相結合,從正在構建的產品的新功能到重構,再到接觸新的全球受眾。 因此,每次關於進一步發展的對話發生時,性能也是對話的一部分。 當代碼庫是新鮮的或剛剛被重構時,實現性能目標要容易得多。
此外,正如 Patrick Meenan 所建議的,在設計過程中計劃加載順序和權衡是值得的。 如果您儘早確定哪些部分更關鍵,並定義它們應該出現的順序,您還將知道什麼可以延遲。 理想情況下,該順序也將反映 CSS 和 JavaScript 導入的順序,因此在構建過程中處理它們會更容易。 此外,請考慮在加載頁面時(例如,尚未加載 Web 字體時)處於“中間”狀態的視覺體驗應該是什麼。
一旦你在你的組織中建立了強大的績效文化,目標是比你以前的自己快 20% ,以便隨著時間的推移保持優先事項的機智(謝謝,Guy Podjarny! )。 但是要考慮客戶的不同類型和使用行為(Tobias Baldauf 稱之為節奏和群組),以及機器人流量和季節性影響。
計劃,計劃,計劃。 儘早進行一些快速的“唾手可得”優化可能很誘人——這可能是一個快速獲勝的好策略——但如果沒有計劃和設置現實,公司將很難將性能放在首位- 量身定制的績效目標。
- 選擇正確的指標。
並非所有指標都同樣重要。 研究哪些指標對您的應用程序最重要:通常,它將由您可以多快開始渲染界面中最重要的像素以及您可以多快為這些渲染像素提供輸入響應來定義。 這些知識將為您提供持續努力的最佳優化目標。 最後,定義體驗的不是加載事件或服務器響應時間,而是對界面感覺有多快的感知。這是什麼意思? 與其關注整個頁面加載時間(例如,通過onLoad和DOMContentLoaded時間),不如優先考慮客戶認為的頁面加載。 這意味著專注於一組略有不同的指標。 事實上,選擇正確的指標是一個沒有明顯贏家的過程。
根據 Tim Kadlec 的研究和 Marcos Iglesias 在他的演講中的筆記,傳統指標可以分為幾組。 通常,我們需要所有這些來全面了解性能,在您的特定情況下,其中一些會比其他更重要。
- 基於數量的指標衡量請求的數量、權重和性能得分。 有利於發出警報和監控隨時間的變化,但不利於理解用戶體驗。
- 里程碑指標使用加載過程生命週期中的狀態,例如Time To First Byte和Time To Interactive 。 適合描述用戶體驗和監控,不太適合了解里程碑之間發生的情況。
- 渲染指標提供對內容渲染速度的估計(例如,開始渲染時間、速度指數)。 適合測量和調整渲染性能,但不適合測量重要內容何時出現並可以與之交互。
- 自定義指標為用戶測量特定的自定義事件,例如 Twitter 的 Time To First Tweet 和 Pinterest 的 PinnerWaitTime。 適合準確描述用戶體驗,但不適合擴展指標和與競爭對手進行比較。
為了完成這幅圖,我們通常會在所有這些組中尋找有用的指標。 通常,最具體和最相關的是:
- 互動時間(TTI)
佈局穩定的點,關鍵的網絡字體可見,主線程足以處理用戶輸入——基本上是用戶可以與 UI 交互的時間標記。 了解用戶必須等待多長時間才能無延遲地使用該網站的關鍵指標。 Boris Schapira 寫了一篇關於如何可靠地測量 TTI 的詳細文章。 - 第一輸入延遲(FID)或輸入響應
從用戶第一次與您的網站交互到瀏覽器實際能夠響應該交互的時間。 很好地補充了 TTI,因為它描述了圖片的缺失部分:當用戶實際與網站交互時會發生什麼。 僅用作 RUM 指標。 在瀏覽器中有一個用於測量 FID 的 JavaScript 庫。 - 最大含量塗料(LCP)
當頁面的重要內容可能已加載時,標記頁面加載時間線中的點。 假設頁面中最重要的元素是用戶視口中可見的最大元素。 如果元素在折疊的上方和下方都呈現,則只有可見部分被認為是相關的。 - 總阻塞時間 ( TBT )
有助於量化頁面在變得可靠交互之前的非交互性嚴重程度的指標(即,主線程在至少 5 秒內沒有運行超過 50 毫秒(長任務)的任何任務)。 該指標測量第一次繪製和交互時間 (TTI) 之間的總時間量,其中主線程被阻塞足夠長的時間以防止輸入響應。 因此,難怪低 TBT 是良好性能的良好指標。 (謝謝,阿爾喬姆,菲爾) - 累積佈局移位 ( CLS )
該指標突出顯示了用戶在訪問網站時遇到意外佈局變化(重排)的頻率。 它檢查了不穩定的元素及其對整體體驗的影響。 分數越低越好。 - 速度指數
衡量頁面內容在視覺上填充的速度; 分數越低越好。 速度指數分數是根據視覺進度的速度計算的,但它只是一個計算值。 它對視口大小也很敏感,因此您需要定義一系列與您的目標受眾相匹配的測試配置。 請注意,隨著 LCP 成為更相關的指標,它變得不那麼重要了(感謝 Boris,Artem! )。 - 花費的 CPU 時間
一個指標,顯示主線程被阻塞的頻率和時間,用於繪畫、渲染、腳本和加載。 高 CPU 時間是janky體驗的明確指標,即當用戶在他們的操作和響應之間體驗到明顯的延遲時。 使用 WebPageTest,您可以在“Chrome”選項卡上選擇“捕獲開發工具時間線”,以在使用 WebPageTest 在任何設備上運行時公開主線程的故障。 - 組件級 CPU 成本
就像所花費的 CPU 時間一樣,這個由 Stoyan Stefanov 提出的指標探討了JavaScript 對 CPU 的影響。 這個想法是使用每個組件的 CPU 指令計數來單獨了解它對整體體驗的影響。 可以使用 Puppeteer 和 Chrome 來實現。 - 挫折指數
雖然上面提到的許多指標解釋了特定事件何時發生,但 Tim Vereecke 的 FrustrationIndex 關注的是指標之間的差距,而不是單獨查看它們。 它著眼於最終用戶感知到的關鍵里程碑,例如標題可見、第一個內容可見、視覺準備就緒和頁面準備就緒,併計算指示加載頁面時的挫敗程度的分數。 差距越大,用戶感到沮喪的機會就越大。 可能是用戶體驗的良好 KPI。 Tim 發表了一篇關於 FrustrationIndex 及其工作原理的詳細文章。 - 廣告權重影響
如果您的網站依賴於廣告產生的收入,那麼跟踪廣告相關代碼的權重很有用。 Paddy Ganti 的腳本構建了兩個 URL(一個正常,一個阻止廣告),通過 WebPageTest 提示生成視頻比較並報告一個增量。 - 偏差指標
正如 Wikipedia 工程師所指出的,您的結果中存在多少差異的數據可以告訴您您的儀器有多可靠,以及您應該對偏差和異常值給予多少關注。 較大的差異表明設置中需要進行調整。 它還有助於了解某些頁面是否更難以可靠地測量,例如由於第三方腳本導致顯著變化。 跟踪瀏覽器版本以了解推出新瀏覽器版本時的性能變化可能也是一個好主意。 - 自定義指標
自定義指標由您的業務需求和客戶體驗定義。 它要求您識別重要的像素、關鍵腳本、必要的 CSS 和相關資產,並衡量它們交付給用戶的速度。 為此,您可以監控英雄渲染時間,或使用性能 API,為對您的業務重要的事件標記特定時間戳。 此外,您可以通過在測試結束時執行任意 JavaScript 來使用 WebPagetest 收集自定義指標。
請注意,第一次有意義的繪畫(FMP)沒有出現在上面的概述中。 它用於深入了解服務器輸出任何數據的速度。 Long FMP 通常表示 JavaScript 阻塞了主線程,但也可能與後端/服務器問題有關。 但是,該指標最近已被棄用,因為它在大約 20% 的情況下似乎不准確。 它被有效地替換為更可靠且更易於推理的 LCP。 Lighthouse 不再支持它。 仔細檢查以用戶為中心的最新性能指標和建議,以確保您在安全頁面上(感謝 Patrick Meenan )。
Steve Souders 詳細解釋了其中許多指標。 重要的是要注意,雖然 Time-To-Interactive 是通過在所謂的實驗室環境中運行自動審計來衡量的,但 First Input Delay 代表實際的用戶體驗,實際用戶會遇到明顯的滯後。 一般來說,始終測量和跟踪它們可能是一個好主意。
根據您的應用程序的上下文,首選指標可能會有所不同:例如,對於 Netflix TV UI,按鍵輸入響應能力、內存使用率和 TTI 更為關鍵,而對於 Wikipedia,第一次/最後一次視覺變化和 CPU 時間花費指標更為重要。
注意:FID 和 TTI 都不考慮滾動行為; 滾動可以獨立發生,因為它是脫離主線程的,所以對於許多內容消費網站來說,這些指標可能不那麼重要(謝謝,Patrick! )。
- 測量和優化 Core Web Vitals 。
長期以來,性能指標都是相當技術性的,專注於服務器響應速度和瀏覽器加載速度的工程視圖。 多年來,這些指標發生了變化——試圖找到一種方法來捕捉實際的用戶體驗,而不是服務器時間。 2020 年 5 月,Google 發布了 Core Web Vitals,這是一組以用戶為中心的新性能指標,每個指標都代表了用戶體驗的不同方面。對於其中的每一個,谷歌都推薦了一系列可接受的速度目標。 至少75% 的頁面瀏覽量應超過良好範圍才能通過此評估。 這些指標迅速獲得關注,隨著 Core Web Vitals 在 2021 年 5 月成為 Google 搜索的排名信號(頁面體驗排名算法更新),許多公司將注意力轉向了他們的績效得分。
讓我們一一分解每個核心網絡生命力,以及有用的技術和工具,以優化您在考慮這些指標的情況下的體驗。 (值得注意的是,通過遵循本文中的一般建議,您最終會獲得更好的 Core Web Vitals 分數。)
- 最大內容塗料( LCP ) < 2.5 秒。
測量頁面的加載,並報告在視口中可見的最大圖像或文本塊的渲染時間。 因此,LCP 會受到所有延遲渲染重要信息的因素的影響——無論是服務器響應速度慢、CSS 阻塞、運行中的 JavaScript(第一方或第三方)、Web 字體加載、昂貴的渲染或繪畫操作、懶惰- 加載的圖像、骨架屏幕或客戶端渲染。
為了獲得良好的體驗,LCP 應在頁面首次開始加載後的2.5 秒內發生。 這意味著我們需要儘早渲染頁面的第一個可見部分。 這將需要為每個模板定制關鍵 CSS,編排<head>
-order 並預取關鍵資產(我們稍後會介紹)。LCP 分數低的主要原因通常是圖像。 要在 Fast 3G 上以 <2.5 秒的時間交付 LCP(託管在經過良好優化的服務器上,全是靜態的,沒有客戶端渲染並且圖像來自專用圖像 CDN)意味著最大理論圖像大小僅為 144KB 左右。 這就是為什麼響應式圖像很重要,以及提前預加載關鍵圖像(使用
preload
)。快速提示:要發現頁面上的 LCP,在 DevTools 中,您可以將鼠標懸停在性能面板中“Timings”下的 LCP 徽章上(感謝 Tim Kadlec !)。
- 首次輸入延遲( FID ) < 100ms。
衡量 UI 的響應能力,即瀏覽器在對離散的用戶輸入事件(如點擊或單擊)做出反應之前忙於其他任務的時間。 它旨在捕獲因主線程繁忙而導致的延遲,尤其是在頁面加載期間。
目標是每次交互都保持在 50-100 毫秒內。 為此,我們需要識別長任務(阻塞主線程超過 50 毫秒)並將它們分解,將一個包代碼拆分為多個塊,減少 JavaScript 執行時間,優化數據獲取,延遲第三方的腳本執行,將 JavaScript 移動到 Web 工作者的後台線程,並使用漸進式水化來降低 SPA 中的再水化成本。快速提示:一般來說,獲得更好 FID 分數的可靠策略是通過將較大的捆綁包分解成較小的捆綁包並在用戶需要時提供所需的服務,從而最大限度地減少主線程上的工作,因此不會延遲用戶交互. 我們將在下面詳細介紹。
- 累積佈局偏移( CLS ) < 0.1。
測量 UI 的視覺穩定性以確保流暢和自然的交互,即頁面生命週期內發生的每次意外佈局轉換的所有單獨佈局轉換分數的總和。 每當已經可見的元素更改其在頁面上的位置時,就會發生單獨的佈局轉換。 它的評分基於內容的大小和移動的距離。
因此,每次出現轉變時——例如,當備用字體和網絡字體具有不同的字體指標,或者廣告、嵌入或 iframe 遲到,或者圖像/視頻尺寸未保留,或者後期 CSS 強制重繪,或者更改由後期 JavaScript——它對 CLS 分數有影響。 良好體驗的推薦值是 CLS < 0.1。
值得注意的是,Core Web Vitals 應該隨著時間的推移而發展,具有可預測的年度週期。 對於第一年的更新,我們可能期望將 First Contentful Paint 提升為 Core Web Vitals,降低 FID 閾值並更好地支持單頁應用程序。 我們可能還會看到負載增加後對用戶輸入的響應,以及安全、隱私和可訪問性 (!) 考慮。
與 Core Web Vitals 相關,有很多有用的資源和文章值得研究:
- Web Vitals 排行榜可讓您將自己的分數與移動設備、平板電腦、台式機以及 3G 和 4G 上的競爭對手進行比較。
- Core SERP Vitals,一個 Chrome 擴展程序,可在 Google 搜索結果中顯示來自 CrUX 的 Core Web Vitals。
- 佈局轉換 GIF 生成器,使用簡單的 GIF 可視化 CLS(也可從命令行獲得)。
- web-vitals 庫可以收集核心 Web Vitals 並將其發送到 Google Analytics、Google Tag Manager 或任何其他分析端點。
- 使用 WebPageTest 分析 Web Vitals,Patrick Meenan 在其中探討了 WebPageTest 如何公開有關 Core Web Vitals 的數據。
- 使用 Core Web Vitals 進行優化,Addy Osmani 的 50 分鐘視頻,其中他重點介紹瞭如何在電子商務案例研究中改進 Core Web Vitals。
- Cumulative Layout Shift in Practice 和 Cumulative Layout Shift in the Real World 是 Nic Jansma 撰寫的綜合性文章,幾乎涵蓋了有關 CLS 的所有內容,以及它與跳出率、會話時間或 Rage Clicks 等關鍵指標的關係。
- What Forces Reflow,帶有屬性或方法的概述,當在 JavaScript 中請求/調用時,將觸發瀏覽器同步計算樣式和佈局。
- CSS Triggers 顯示了哪些 CSS 屬性觸發了 Layout、Paint 和 Composite。
- Fixing Layout Instability 是使用 WebPageTest 來識別和修復佈局不穩定問題的演練。
- Cumulative Layout Shift, The Layout Instability Metric,Boris Schapira 關於 CLS 的另一個非常詳細的指南,它是如何計算的,如何測量以及如何優化它。
- How To Improvement Core Web Vitals,Simon Hearne 關於每個指標(包括其他 Web Vitals,例如 FCP、TTI、TBT)的詳細指南,以及它們何時發生以及如何衡量。
那麼,Core Web Vitals 是要遵循的最終指標嗎? 不完全的。 它們確實已經暴露在大多數 RUM 解決方案和平台中,包括 Cloudflare、Treo、SpeedCurve、Calibre、WebPageTest(已經在幻燈片視圖中)、Newrelic、Shopify、Next.js、所有 Google 工具(PageSpeed Insights、Lighthouse + CI、Search控制台等)和許多其他。
然而,正如 Katie Sylor-Miller 解釋的那樣,Core Web Vitals 的一些主要問題是缺乏跨瀏覽器支持,我們並沒有真正衡量用戶體驗的整個生命週期,而且很難關聯 FID 和具有業務成果的 CLS。
由於我們應該期待 Core Web Vitals 不斷發展,因此始終將Web Vitals 與您定制的指標結合起來似乎是合理的,以更好地了解您在性能方面的立場。
- 最大內容塗料( LCP ) < 2.5 秒。
- 在代表您的受眾的設備上收集數據。
為了收集準確的數據,我們需要徹底選擇要測試的設備。 在大多數公司中,這意味著研究分析並根據最常見的設備類型創建用戶配置文件。 然而,通常僅靠分析並不能提供完整的畫面。 很大一部分目標受眾可能會因為體驗太慢而放棄該網站(並且不再返回),因此他們的設備不太可能成為分析中最受歡迎的設備。 因此,另外對目標群體中的常用設備進行研究可能是一個好主意。根據 IDC 的數據,在 2020 年全球範圍內,所有出貨的手機中有 84.8% 是 Android 設備。 普通消費者每 2 年升級一次手機,在美國,手機更換週期為 33 個月。 全球最暢銷的手機平均售價不到 200 美元。
那麼,一個具有代表性的設備是至少 24 個月大的 Android 設備,成本為 200 美元或更少,運行速度較慢,3G,400ms RTT 和 400kbps 傳輸,只是稍微悲觀一些。 當然,這對於您的公司來說可能會非常不同,但這已經足夠接近大多數客戶了。 事實上,為您的目標市場調查當前的亞馬遜暢銷書可能是一個好主意。 (感謝 Tim Kadlec、Henri Helvetica 和 Alex Russell 的指點! )。
那麼選擇什麼測試設備呢? 與上面概述的配置文件非常吻合的那些。 選擇稍舊的 Moto G4/G5 Plus、中端三星設備(Galaxy A50、S8)、中端設備(如 Nexus 5X、小米 Mi A3 或小米紅米 Note)是一個不錯的選擇7 和慢速設備,如 Alcatel 1X 或 Cubot X19,可能在開放設備實驗室中。 要在速度較慢的熱節流設備上進行測試,您還可以購買 Nexus 4,價格僅為 100 美元左右。
此外,請檢查每台設備中使用的芯片組,不要過度代表一個芯片組:幾代 Snapdragon 和 Apple 以及低端的瑞芯微、聯發科就足夠了(謝謝,帕特里克!) 。
如果您手頭沒有設備,可以通過在受限制的 3G 網絡(例如 300 毫秒 RTT、1.6 Mbps 下行、0.8 Mbps 上行)和 CPU 限制(5 倍減速)上進行測試來模擬台式機上的移動體驗。 最終切換到常規 3G、慢速 4G(例如 170 毫秒 RTT、9 Mbps 下行、9Mbps 上行)和 Wi-Fi。 為了使性能影響更加明顯,您甚至可以引入 2G 星期二或在辦公室設置節流 3G/4G 網絡以加快測試速度。
請記住,在移動設備上,與台式機相比,我們應該期待 4 倍至 5 倍的減速。 移動設備具有不同的 GPU、CPU、內存和不同的電池特性。 這就是為什麼擁有一個普通設備的良好配置文件並始終在這樣的設備上進行測試很重要的原因。
- 綜合測試工具通過預定義的設備和網絡設置(例如Lighthouse 、 Calibre 、 WebPageTest )和
- 真實用戶監控( RUM ) 工具持續評估用戶交互並收集現場數據(例如SpeedCurve 、 New Relic——這些工具也提供綜合測試)。
- 使用 Lighthouse CI 隨時間跟踪 Lighthouse 分數(這非常令人印象深刻),
- 在 GitHub Actions 中運行 Lighthouse 以在每個 PR 旁邊獲得一份 Lighthouse 報告,
- 在站點的每個頁面上運行 Lighthouse 性能審計(通過 Lightouse Parade),輸出保存為 CSV,
- 如果您需要深入了解更多細節,請使用 Lighthouse 分數計算器和 Lighthouse 指標權重。
- Lighthouse 也可用於 Firefox,但在底層它使用 PageSpeed Insights API 並基於無頭 Chrome 79 用戶代理生成報告。
幸運的是,有許多很棒的選項可以幫助您自動收集數據並根據這些指標衡量您的網站在一段時間內的表現。 請記住,良好的性能圖片涵蓋一組性能指標、實驗室數據和現場數據:
前者在開發過程中特別有用,因為它可以幫助您在開發產品時識別、隔離和修復性能問題。 後者對於長期維護很有用,因為它將幫助您了解實時發生的性能瓶頸——當用戶實際訪問該站點時。
通過利用內置的 RUM API,例如導航計時、資源計時、繪製計時、長任務等,綜合測試工具和 RUM 一起提供了應用程序性能的完整圖景。 您可以使用 Calibre、Treo、SpeedCurve、mPulse 和 Boomerang、Sitespeed.io,它們都是性能監控的絕佳選擇。 此外,使用 Server Timing 標頭,您甚至可以在一個地方監控後端和前端性能。
注意:選擇瀏覽器外部的網絡級節流器總是更安全的選擇,例如,DevTools 與 HTTP/2 推送交互時存在問題,這是由於它的實現方式(感謝 Yoav,Patrick !)。 對於 Mac OS,我們可以使用 Network Link Conditioner、Windows Windows Traffic Shaper、Linux netem 和 FreeBSD dummynet。
由於您可能會在 Lighthouse 中進行測試,請記住您可以:
- 設置“乾淨”和“客戶”配置文件進行測試。
在被動監控工具中運行測試時,關閉防病毒和後台 CPU 任務、刪除後台帶寬傳輸並使用沒有瀏覽器擴展的干淨用戶配置文件進行測試以避免結果偏差(在 Firefox 和 Chrome 中)是一種常見策略。但是,研究您的客戶經常使用哪些瀏覽器擴展,並使用專門的“客戶”配置文件進行測試也是一個好主意。 事實上,某些擴展程序可能會對您的應用程序產生深遠的性能影響(2020 Chrome 擴展程序性能報告),如果您的用戶經常使用它們,您可能需要預先考慮。 因此,僅“乾淨”的配置文件結果就過於樂觀,在現實生活中可能會被粉碎。
- 與您的同事分享績效目標。
確保團隊中的每個成員都熟悉績效目標,以避免產生誤解。 每一個決定——無論是設計、營銷還是介於兩者之間的任何決定——都會對績效產生影響,在整個團隊中分配責任和所有權將簡化以後以績效為中心的決策。 根據性能預算和早期定義的優先級映射設計決策。
設定切合實際的目標
- 100 毫秒響應時間,60 fps。
為了讓交互感覺流暢,界面有 100 毫秒來響應用戶的輸入。 再長一點,用戶就會認為應用程序滯後。 以用戶為中心的性能模型 RAIL 為您提供了健康的目標:為了允許 <100 毫秒的響應,頁面必須最遲在每 <50 毫秒後將控制權交還給主線程。 估計輸入延遲告訴我們是否達到了該閾值,理想情況下,它應該低於 50 毫秒。 對於動畫這樣的高壓點,能做的最好什麼都不做,不能做的絕對最少。此外,每一幀動畫應該在 16 毫秒內完成,從而達到每秒 60 幀(1 秒 ÷ 60 = 16.6 毫秒)——最好在 10 毫秒以下。 因為瀏覽器需要時間將新幀繪製到屏幕上,所以您的代碼應該在達到 16.6 毫秒標記之前完成執行。 我們開始討論 120fps(例如 iPad Pro 的屏幕以 120Hz 運行),Surma 已經介紹了一些 120fps 的渲染性能解決方案,但這可能不是我們目前正在關注的目標。
對性能預期持悲觀態度,但對界面設計持樂觀態度並明智地使用空閒時間(檢查 idlize、idle-until-urgent 和 react-idle)。 顯然,這些目標適用於運行時性能,而不是加載性能。
- FID < 100ms, LCP < 2.5s, TTI < 5s on 3G, 關鍵文件大小預算 < 170KB (gzipped)。
雖然這可能很難實現,但一個好的最終目標是 Time to Interactive 低於 5 秒,而對於重複訪問,目標是低於 2 秒(只有通過服務人員才能實現)。 以 2.5 秒以下的最大內容繪製為目標,並最大限度地減少總阻塞時間和累積佈局偏移。 可接受的首次輸入延遲低於 100 毫秒至 70 毫秒。 如上所述,我們考慮的基準是 200 美元的 Android 手機(例如 Moto G4),在慢速 3G 網絡上,模擬 400ms RTT 和 400kbps 傳輸速度。我們有兩個主要的限制因素,它們有效地形成了在網絡上快速交付內容的合理目標。 一方面,由於 TCP 慢啟動,我們有網絡傳輸限制。 HTML 的前 14KB——10 個 TCP 數據包,每個 1460 字節,大約 14.25 KB,儘管不是字面意思——是最關鍵的有效負載塊,也是預算中唯一可以在第一次往返中交付的部分(由於移動喚醒時間,這是您在 400 毫秒 RTT 時 1 秒內獲得的所有數據)。
(注意:由於 TCP 通常在很大程度上未充分利用網絡連接,因此 Google 開發了 TCP 瓶頸帶寬和 RRT( BBR ),這是一種 TCP 延遲控制的 TCP 流量控制算法。專為現代網絡設計,它可以響應實際的擁塞,而不是像 TCP 那樣丟包,它明顯更快,具有更高的吞吐量和更低的延遲——並且算法的工作方式不同。(謝謝,維克多,巴里! )
另一方面,由於 JavaScript 解析和執行時間,我們對內存和 CPU 有硬件限制(我們稍後會詳細討論)。 為了實現第一段中所述的目標,我們必須考慮 JavaScript 的關鍵文件大小預算。 對於預算應該是多少(這在很大程度上取決於您的項目的性質),意見各不相同,但 170KB JavaScript gzip 的預算已經在中端手機上解析和編譯需要 1 秒。 假設 170KB 在解壓後擴展為該大小的 3 倍(0.7MB),這可能是 Moto G4/G5 Plus 上“體面”用戶體驗的喪鐘。
以 Wikipedia 網站為例,2020 年,在全球範圍內,Wikipedia 用戶的代碼執行速度提高了 19%。 因此,如果您的年度網絡性能指標保持穩定,這通常是一個警告信號,因為隨著環境的不斷改善,您實際上正在倒退(Gilles Dubuc 的博客文章中有詳細信息)。
如果您想瞄準東南亞、非洲或印度等成長型市場,您將不得不考慮一組截然不同的限制條件。 Addy Osmani 涵蓋了主要的功能手機限制,例如低成本、高質量的設備、高質量網絡的不可用和昂貴的移動數據——以及這些環境的PRPL-30 預算和開髮指南。
事實上,Google 的 Alex Russell 建議將 gzip 壓縮後的 130-170KB 作為合理的上限。 在現實世界的場景中,大多數產品甚至都沒有接近:今天的中位數捆綁大小約為 452KB,與 2015 年初相比增長了 53.6%。在中產階級移動設備上,這佔 12-20 秒的時間-To-Interactive 。
不過,我們也可以超出捆綁包大小的預算。 例如,我們可以根據瀏覽器主線程的活動設置性能預算,即開始渲染之前的繪製時間,或跟踪前端 CPU 佔用情況。 Calibre、SpeedCurve 和 Bundlesize 等工具可以幫助您控制預算,並且可以集成到您的構建過程中。
最後,性能預算可能不應該是一個固定值。 根據網絡連接,性能預算應該適應,但較慢連接的有效負載更加“昂貴”,無論它們如何使用。
注意:在廣泛傳播的 HTTP/2、即將到來的 5G 和 HTTP/3、快速發展的手機和蓬勃發展的 SPA 時代,設置如此嚴格的預算可能聽起來很奇怪。 然而,當我們處理網絡和硬件的不可預測性時,它們聽起來確實是合理的,包括從擁擠的網絡到緩慢發展的基礎設施,再到數據上限、代理瀏覽器、保存數據模式和偷偷摸摸的漫遊費用。
定義環境
- 選擇並設置您的構建工具。
不要過分關注這些天所謂的酷。 堅持您的構建環境,無論是 Grunt、Gulp、Webpack、Parcel 還是工具組合。 只要你得到你需要的結果並且你在維護你的構建過程沒有問題,你就做得很好。在構建工具中,Rollup 和 Snowpack 一直受到關注,但 Webpack 似乎是最成熟的工具,有數百個插件可用於優化構建的大小。 注意 Webpack 路線圖 2021。
最近出現的最著名的策略之一是在 Next.js 和 Gatsby 中使用 Webpack 進行粒度分塊,以最大限度地減少重複代碼。 默認情況下,可以為不使用它的路由請求未在每個入口點共享的模塊。 這最終成為一種開銷,因為下載的代碼比必要的多。 通過 Next.js 中的粒度分塊,我們可以使用服務器端構建清單文件來確定哪些輸出的塊被不同的入口點使用。
使用 SplitChunksPlugin,根據多個條件創建多個拆分塊,以防止跨多個路由獲取重複代碼。 這改善了導航期間的頁面加載時間和緩存。 在 Next.js 9.2 和 Gatsby v2.20.7 中發布。
不過,開始使用 Webpack 可能會很困難。 所以如果你想深入研究 Webpack,這裡有一些很棒的資源:
- Webpack 文檔——顯然——是一個很好的起點,Webpack - Raja Rao 的 The Confusing Bits 和 Andrew Welch 的 An Annotated Webpack Config 也是如此。
- Sean Larkin 有一個關於 Webpack 的免費課程:核心概念和 Jeffrey Way 為每個人發布了一個很棒的關於 Webpack 的免費課程。 它們都是深入研究 Webpack 的絕佳介紹。
- Webpack Fundamentals 是一個非常全面的 4 小時課程,由 FrontendMasters 發布,由 Sean Larkin 教授。
- Webpack 示例有數百個現成的 Webpack 配置,按主題和用途分類。 獎勵:還有一個生成基本配置文件的 Webpack 配置配置器。
- awesome-webpack 是一個有用的 Webpack 資源、庫和工具的精選列表,包括 Angular、React 和框架無關項目的文章、視頻、課程、書籍和示例。
- 使用 Webpack 快速構建資產的旅程是 Etsy 的案例研究,該案例研究團隊如何從使用基於 RequireJS 的 JavaScript 構建系統切換到使用 Webpack,以及他們如何優化他們的構建,平均在4 分鐘內管理超過 13,200 個資產。
- Webpack 性能技巧是 Ivan Akulov 的一個金礦主題,其中包含許多以性能為中心的技巧,包括專門針對 Webpack 的技巧。
- awesome-webpack-perf 是一個金礦 GitHub 存儲庫,其中包含有用的 Webpack 工具和插件以提高性能。 也由 Ivan Akulov 維護。
- 使用漸進增強作為默認值。
儘管如此,經過這麼多年,將漸進增強作為前端架構和部署的指導原則是一個安全的選擇。 首先設計和構建核心體驗,然後使用功能強大的瀏覽器的高級功能增強體驗,創造彈性體驗。 如果您的網站在次優網絡上運行速度較慢且瀏覽器較差且屏幕較差的機器上運行得很快,那麼它只會在速度較快且瀏覽器良好且網絡良好的機器上運行得更快。事實上,通過自適應模塊服務,我們似乎正在將漸進增強提升到另一個層次,為低端設備提供“精簡”核心體驗,並為高端設備提供更複雜的功能。 漸進式增強不太可能很快消失。
- 選擇強大的性能基準。
有很多未知因素影響加載——網絡、熱限制、緩存驅逐、第三方腳本、解析器阻塞模式、磁盤 I/O、IPC 延遲、安裝的擴展、防病毒軟件和防火牆、後台 CPU 任務、硬件和內存限制, L2/L3 緩存、RTTS 的差異——JavaScript 的體驗成本最高,僅次於默認阻止渲染的 Web 字體和通常消耗過多內存的圖像。 隨著性能瓶頸從服務器轉移到客戶端,作為開發人員,我們必須更詳細地考慮所有這些未知因素。170KB 的預算已經包含關鍵路徑 HTML/CSS/JavaScript、路由器、狀態管理、實用程序、框架和應用程序邏輯,我們必須徹底檢查網絡傳輸成本、解析/編譯時間和運行時成本我們選擇的框架。 幸運的是,在過去幾年中,我們已經看到瀏覽器解析和編譯腳本的速度有了巨大的改進。 然而,JavaScript 的執行仍然是主要瓶頸,因此密切關註腳本執行時間和網絡可能會產生影響。
Tim Kadlec 對現代框架的性能進行了出色的研究,並在“JavaScript 框架有成本”一文中對其進行了總結。 我們經常談論獨立框架的影響,但正如 Tim 所說,在實踐中,使用多個框架並不少見。 也許是舊版本的 jQuery 正在慢慢遷移到現代框架,以及一些使用舊版本 Angular 的遺留應用程序。 因此,探索 JavaScript 字節和 CPU 執行時間的累積成本更合理,這很容易使用戶體驗幾乎無法使用,即使在高端設備上也是如此。
一般來說,現代框架不會優先考慮功能較弱的設備,因此手機和台式機上的體驗在性能方面通常會大不相同。 根據研究,使用 React 或 Angular 的網站在 CPU 上花費的時間比其他網站多(當然這並不一定說 React 在 CPU 上比 Vue.js 更昂貴)。
根據 Tim 的說法,有一件事是顯而易見的:“如果您使用框架來構建您的網站,那麼您就需要在初始性能方面進行權衡——即使在最好的情況下也是如此。”
- 評估框架和依賴項。
現在,並不是每個項目都需要一個框架,也不是單頁應用程序的每個頁面都需要加載一個框架。 在 Netflix 的案例中,“從客戶端移除 React、多個庫和相應的應用程序代碼將 JavaScript 的總量減少了 200 KB 以上,導致 Netflix 的註銷主頁的 Time-to-Interactivity 減少了 50% 以上。” 然後,團隊利用用戶在登陸頁面上花費的時間來為用戶可能登陸的後續頁面預取 React(請繼續閱讀以了解詳細信息)。那麼,如果您完全刪除關鍵頁面上的現有框架怎麼辦? 使用 Gatsby,您可以檢查 gatsby-plugin-no-javascript,它會從靜態 HTML 文件中刪除 Gatsby 創建的所有 JavaScript 文件。 在 Vercel 上,您還可以允許在生產中為某些頁面禁用運行時 JavaScript(實驗性)。
一旦選擇了一個框架,我們將至少使用它幾年,所以如果我們需要使用一個框架,我們需要確保我們的選擇是明智的和經過深思熟慮的——尤其是對於我們的關鍵性能指標關心。
數據顯示,默認情況下,框架非常昂貴:58.6% 的 React 頁面交付超過 1 MB 的 JavaScript,36% 的 Vue.js 頁面加載的 First Contentful Paint 小於 1.5 秒。 根據 Ankur Sethi 的一項研究,“無論你如何優化它,你的 React 應用程序在印度普通手機上的加載速度永遠不會超過 1.1 秒。你的 Angular 應用程序總是至少需要 2.7 秒才能啟動。您的 Vue 應用程序的用戶需要等待至少 1 秒鐘才能開始使用它。” 無論如何,您可能不會將印度作為您的主要市場,但在網絡條件欠佳的情況下訪問您的網站的用戶將獲得類似的體驗。
當然,可以快速製作 SPA,但它們並不是開箱即用的快速,因此我們需要考慮製作和保持快速所需的時間和精力。 儘早選擇輕量級的基準性能成本可能會更容易。
那麼我們如何選擇框架呢? 在選擇選項之前,至少考慮大小的總成本 + 初始執行時間是個好主意; Preact、Inferno、Vue、Svelte、Alpine 或 Polymer 等輕量級選項可以很好地完成工作。 基線的大小將定義應用程序代碼的約束。
正如 Seb Markbage 所指出的,衡量框架啟動成本的一個好方法是首先渲染一個視圖,然後刪除它,然後再次渲染,因為它可以告訴您框架如何擴展。 第一次渲染往往會預熱一堆延遲編譯的代碼,更大的樹在擴展時可以從中受益。 第二個渲染基本上是模擬頁面上的代碼重用如何隨著頁面複雜性的增長而影響性能特徵。
您可以通過探索功能、可訪問性、穩定性、性能、包生態系統、社區、學習曲線、文檔、工具、跟踪記錄,在 Sacha Greif 的 12 分制評分系統上評估您的候選人(或一般的任何 JavaScript 庫) 、團隊、兼容性、安全性等。
您還可以依賴在較長時間內在網絡上收集的數據。 例如,Perf Track 大規模跟踪框架性能,顯示使用 Angular、React、Vue、Polymer、Preact、Ember、Svelte 和 AMP 構建的網站的原始聚合Core Web Vitals 分數。 您甚至可以指定和比較使用 Gatsby、Next.js 或 Create React App 構建的網站,以及使用 Nuxt.js (Vue) 或 Sapper (Svelte) 構建的網站。
一個好的起點是為您的應用程序選擇一個好的默認堆棧。 Gatsby (React)、Next.js (React)、Vuepress (Vue)、Preact CLI 和 PWA Starter Kit 提供了合理的默認值,可以在普通移動硬件上開箱即用地快速加載。 另外,請查看針對 React 和 Angular 的 web.dev 框架特定的性能指南(感謝 Phillip! )。
或許您可以採用一種更令人耳目一新的方法來完全構建單頁應用程序——Turbolinks,一個 15KB 的 JavaScript 庫,它使用 HTML 而不是 JSON 來呈現視圖。 因此,當您點擊鏈接時,Turbolinks 會自動獲取頁面,交換其
<body>
並合併其<head>
,所有這些都不會產生整個頁面加載的成本。 您可以查看有關堆棧(Hotwire)的快速詳細信息和完整文檔。
- 客戶端渲染還是服務器端渲染? 兩個都!
這是一個相當激烈的談話。 最終的方法是設置某種漸進式引導:使用服務器端渲染來獲得快速的 First Contentful Paint,但還包括一些最少的必要 JavaScript 以保持與 First Contentful Paint 接近的交互時間。 如果在 FCP 之後 JavaScript 來得太晚,瀏覽器會在解析、編譯和執行後期發現的 JavaScript 時鎖定主線程,從而束縛站點或應用程序的交互性。為避免這種情況,請始終將函數的執行分解為單獨的異步任務,並在可能的情況下使用
requestIdleCallback
。 考慮使用 WebPack 的動態import()
支持延遲加載 UI 的部分,避免加載、解析和編譯成本,直到用戶真正需要它們(感謝 Addy! )。如上所述,交互時間 (TTI) 告訴我們導航和交互之間的時間。 具體來說,該指標是通過查看初始內容呈現後的第一個 5 秒窗口來定義的,其中沒有任何 JavaScript 任務花費超過 50 毫秒(長任務)。 如果發生超過 50 毫秒的任務,則重新開始搜索 5 秒窗口。 結果,瀏覽器將首先假定它到達Interactive ,只是為了切換到Frozen ,最終切換回Interactive 。
一旦我們到達Interactive ,我們就可以 - 按需或在時間允許的情況下 - 啟動應用程序的非必要部分。 不幸的是,正如 Paul Lewis 所注意到的,框架通常沒有可以向開發人員展示的簡單優先級概念,因此對於大多數庫和框架來說,漸進式引導並不容易實現。
不過,我們正在到達那裡。 這些天來,我們可以探索幾個選擇,Houssein Djirdeh 和 Jason Miller 在他們關於 Rendering on the Web 的演講以及 Jason 和 Addy 關於現代前端架構的文章中對這些選項進行了很好的概述。 下面的概述基於他們的出色工作。
- 完整的服務器端渲染(SSR)
在 WordPress 等經典 SSR 中,所有請求都完全在服務器上處理。 請求的內容作為完成的 HTML 頁面返回,瀏覽器可以立即呈現它。 因此,例如,SSR 應用程序不能真正使用 DOM API。 First Contentful Paint 和 Time to Interactive 之間的差距通常很小,並且頁面可以在 HTML 流式傳輸到瀏覽器時立即呈現。這避免了在客戶端獲取數據和模板的額外往返,因為它是在瀏覽器得到響應之前處理的。 但是,我們最終會得到更長的服務器思考時間,從而導致第一個字節的時間,並且我們沒有利用現代應用程序的響應式和豐富的特性。
- 靜態渲染
我們將產品構建為單頁應用程序,但所有頁面都使用最少的 JavaScript 作為構建步驟預先呈現為靜態 HTML。 這意味著通過靜態渲染,我們可以提前為每個可能的 URL生成單獨的 HTML 文件,這是很多應用程序無法承受的。 但是由於不必動態生成頁面的 HTML,我們可以實現始終如一的快速首字節時間。 因此,我們可以快速顯示一個登錄頁面,然後為後續頁面預取一個 SPA 框架。 Netflix 採用了這種方法,將加載和交互時間減少了 50%。 - 使用(重新)水合的服務器端渲染(通用渲染,SSR + CSR)
我們可以嘗試使用兩全其美的方法——SSR 和 CSR 方法。 通過混合水化,從服務器返回的 HTML 頁面還包含一個腳本,用於加載成熟的客戶端應用程序。 理想情況下,實現快速的 First Contentful Paint(如 SSR),然後通過(重新)水化繼續渲染。 不幸的是,這種情況很少見。 更常見的情況是,頁面看起來確實準備好了,但它無法響應用戶的輸入,從而產生憤怒的點擊和放棄。使用 React,我們可以在 Express 等 Node 服務器上使用
ReactDOMServer
模塊,然後調用renderToString
方法將頂級組件呈現為靜態 HTML 字符串。使用 Vue.js,我們可以使用 vue-server-renderer 使用
renderToString
將 Vue 實例渲染為 HTML。 在 Angular 中,我們可以使用@nguniversal
將客戶端請求轉換為完全由服務器渲染的 HTML 頁面。 使用 Next.js (React) 或 Nuxt.js (Vue) 也可以開箱即用地實現完全服務器渲染的體驗。這種方法有其缺點。 因此,我們確實獲得了客戶端應用程序的完全靈活性,同時提供了更快的服務器端渲染,但我們最終也會在 First Contentful Paint 和 Time To Interactive 之間存在更長的差距,並且增加了 First Input Delay。 補液非常昂貴,通常僅此策略還不夠好,因為它會嚴重延遲 Time To Interactive。
- 使用漸進式水化 (SSR + CSR) 的流式服務器端渲染
為了最小化 Time To Interactive 和 First Contentful Paint 之間的差距,我們一次渲染多個請求,並在生成內容時分塊發送內容。 因此,我們不必在將內容髮送到瀏覽器之前等待完整的 HTML 字符串,從而改進了 Time To First Byte。在 React 中,我們可以使用 renderToNodeStream() 來代替
renderToString()
() 來管道響應並以塊的形式發送 HTML。 在 Vue 中,我們可以使用可以管道和流式傳輸的 renderToStream()。 使用 React Suspense,我們也可以為此目的使用異步渲染。在客戶端,我們不是一次啟動整個應用程序,而是逐步啟動組件。 應用程序的部分首先通過代碼拆分分解為獨立的腳本,然後逐漸水合(按照我們的優先級順序)。 事實上,我們可以先對關鍵成分進行水合,然後再對其餘成分進行水合。 然後,每個組件可以不同地定義客戶端和服務器端渲染的角色。 然後,我們還可以推遲某些組件的水合,直到它們出現,或者用戶交互需要,或者瀏覽器空閒時。
對於 Vue,Markus Oberlehner 發布了一份關於使用用戶交互水合以及 vue-lazy-hydration 減少 SSR 應用程序的交互時間的指南,這是一個早期插件,可在可見性或特定用戶交互上啟用組件水合。 Angular 團隊使用 Ivy Universal 進行漸進式補水。 您也可以使用 Preact 和 Next.js 實現部分水合。
- 三態渲染
有了服務工作者,我們可以使用流式服務器渲染來進行初始/非 JS 導航,然後讓服務工作者在安裝後為導航渲染 HTML。 在這種情況下,Service Worker 會預先呈現內容並啟用 SPA 風格的導航,以便在同一會話中呈現新視圖。 當您可以在服務器、客戶端頁面和服務工作者之間共享相同的模板和路由代碼時,效果很好。
- 帶有預渲染的 CSR
預渲染類似於服務器端渲染,但不是在服務器上動態渲染頁面,而是在構建時將應用程序渲染為靜態 HTML。 雖然靜態頁面無需太多客戶端 JavaScript 即可完全交互,但預渲染的工作方式有所不同。 基本上,它在構建時將客戶端應用程序的初始狀態捕獲為靜態 HTML,而通過預渲染,必須在客戶端上啟動應用程序才能使頁面具有交互性。使用 Next.js,我們可以通過將應用程序預渲染為靜態 HTML 來使用靜態 HTML 導出。 在 Gatsby 中,一個使用 React 的開源靜態站點生成器,在構建期間使用
renderToStaticMarkup
方法而不是renderToString
方法,預加載主 JS 塊並預取未來路由,沒有簡單靜態頁面不需要的 DOM 屬性。對於 Vue,我們可以使用 Vuepress 來達到同樣的目的。 你也可以在 Webpack 中使用 prerender-loader。 Navi 也提供靜態渲染。
結果是更好的 Time To First Byte 和 First Contentful Paint,我們減少了 Time To Interactive 和 First Contentful Paint 之間的差距。 如果預計內容會發生很大變化,我們就不能使用這種方法。 此外,必須提前知道所有 URL 才能生成所有頁面。 所以一些組件可能會使用預渲染來渲染,但如果我們需要動態的東西,我們必須依賴應用程序來獲取內容。
- 完整的客戶端渲染(CSR)
所有邏輯、渲染和啟動都在客戶端完成。 結果通常是 Time To Interactive 和 First Contentful Paint 之間的巨大差距。 結果,應用程序經常感覺遲緩,因為整個應用程序必須在客戶端上啟動才能呈現任何內容。由於 JavaScript 有性能成本,隨著 JavaScript 的數量隨著應用程序的增長而增長,激進的代碼拆分和延遲 JavaScript 將絕對有必要馴服 JavaScript 的影響。 對於這種情況,如果不需要太多交互性,服務器端渲染通常是更好的方法。 如果這不是一個選項,請考慮使用 App Shell 模型。
一般來說,SSR 比 CSR 快。 然而,對於許多應用程序來說,它仍然是一個相當頻繁的實現。
那麼,客戶端還是服務器端? 一般來說,將完全客戶端框架的使用限制在絕對需要它們的頁面上是一個好主意。 對於高級應用程序,單獨依賴服務器端渲染也不是一個好主意。 如果做得不好,服務器渲染和客戶端渲染都是一場災難。
無論您是傾向於 CSR 還是 SSR,請確保您盡快渲染重要的像素,並將該渲染與 Time To Interactive 之間的差距最小化。 如果您的頁面沒有太大變化,請考慮預渲染,並儘可能推遲框架的啟動。 使用服務器端渲染將 HTML 分塊流式傳輸,並為客戶端渲染實現漸進式水合- 並在可見性、交互或空閒時間進行水合,以獲得兩全其美的效果。
- 完整的服務器端渲染(SSR)
- 我們可以靜態服務多少?
無論您是在處理大型應用程序還是小型站點,都值得考慮哪些內容可以從 CDN(即 JAM 堆棧)靜態提供,而不是動態生成。 即使您擁有數千種產品和數百個具有大量個性化選項的過濾器,您仍可能希望靜態提供關鍵登錄頁面,並將這些頁面與您選擇的框架分離。有很多靜態站點生成器,它們生成的頁面通常非常快。 我們可以提前預構建的內容越多,而不是在請求時在服務器或客戶端上生成頁面視圖,我們將獲得更好的性能。
在構建部分水合、漸進增強的靜態網站中,Markus Oberlehner 展示瞭如何使用靜態站點生成器和 SPA 構建網站,同時實現漸進增強和最小的 JavaScript 包大小。 Markus 使用Eleventy 和 Preact作為他的工具,並展示瞭如何設置工具、添加部分水合、延遲水合、客戶端入口文件、為 Preact 配置 Babel 以及將 Preact 與 Rollup 捆綁在一起——從頭到尾。
如今,隨著 JAMStack 在大型網站上的使用,出現了一個新的性能考慮:構建時間。 事實上,即使每次新部署構建數千個頁面也可能需要幾分鐘,因此很有希望看到 Gatsby 中的增量構建將構建時間縮短60 倍,並集成到流行的 CMS 解決方案中,如 WordPress、Contentful、Drupal、Netlify CMS和別的。
此外,Next.js 宣布了提前和增量靜態生成,它允許我們在運行時添加新的靜態頁面,並在現有頁面已經構建後更新它們,通過在流量進入時在後台重新渲染它們.
需要更輕量級的方法? 在關於 Eleventy、Alpine 和 Tailwind 的演講中:邁向輕量級 Jamstack,Nicola Goutay 解釋了 CSR、SSR 和介於兩者之間的所有內容之間的區別,並展示瞭如何使用更輕量級的方法 - 以及顯示該方法的 GitHub 存儲庫在實踐中。
- 考慮使用 PRPL 模式和 app shell 架構。
不同的框架會對性能產生不同的影響,並且需要不同的優化策略,因此您必須清楚地了解您將依賴的框架的所有細節。 在構建 Web 應用程序時,請查看 PRPL 模式和應用程序外殼架構。 這個想法非常簡單:推送獲得交互所需的最少代碼以使初始路由快速呈現,然後使用 service worker 緩存和預緩存資源,然後異步延遲加載您需要的路由。
- 您是否優化了 API 的性能?
API 是應用程序通過端點向內部和第三方應用程序公開數據的通信渠道。 在設計和構建 API 時,我們需要一個合理的協議來實現服務器和第三方請求之間的通信。 Representational State Transfer ( REST ) 是一種成熟的、合乎邏輯的選擇:它定義了一組開發人員遵循的約束,以使內容以高性能、可靠和可擴展的方式可訪問。 符合 REST 約束的 Web 服務稱為RESTful Web 服務。與良好的 ol' HTTP 請求一樣,當從 API 檢索數據時,服務器響應的任何延遲都會傳播到最終用戶,從而延遲渲染。 當資源想要從 API 中檢索一些數據時,它需要從相應的端點請求數據。 渲染來自多個資源的數據的組件,例如在每個評論中包含評論和作者照片的文章,可能需要多次往返服務器以獲取所有數據,然後才能渲染它。 此外,通過 REST 返回的數據量通常超過渲染該組件所需的數據量。
如果許多資源需要來自 API 的數據,則 API 可能會成為性能瓶頸。 GraphQL 為這些問題提供了一個高性能的解決方案。 就其本身而言,GraphQL 是一種用於 API 的查詢語言,也是一種服務器端運行時,用於使用您為數據定義的類型系統來執行查詢。 與 REST 不同,GraphQL 可以在單個請求中檢索所有數據,並且響應將完全符合要求,而不會像 REST 通常發生的那樣過度或不足地獲取數據。
此外,由於 GraphQL 使用模式(說明數據結構的元數據),它已經可以將數據組織成首選結構,因此,例如,使用 GraphQL,我們可以刪除用於處理狀態管理的 JavaScript 代碼,生成更簡潔的應用程序代碼,在客戶端上運行得更快。
如果您想開始使用 GraphQL 或遇到性能問題,這些文章可能會很有幫助:
- GraphQL 入門:為什麼我們需要一種新的 API,作者 Eric Baer,
- A GraphQL Primer: The Evolution of API Design by Eric Baer,
- Leonardo Losoviz 設計了一個 GraphQL 服務器以獲得最佳性能,
- Wojciech Trocki 解釋了 GraphQL 性能。
- 您會使用 AMP 還是 Instant Articles?
根據您組織的優先事項和戰略,您可能需要考慮使用 Google 的 AMP 或 Facebook 的 Instant Articles 或 Apple 的 Apple News。 沒有它們你也能獲得良好的性能,但 AMP確實提供了一個可靠的性能框架和一個免費的內容交付網絡 (CDN),而 Instant Articles 將提高你在 Facebook 上的知名度和性能。這些技術對用戶來說看似顯而易見的好處是保證了性能,因此有時他們甚至可能更喜歡 AMP-/Apple News/Instant Pages-鏈接,而不是“常規”和可能臃腫的頁面。 對於處理大量第三方內容的內容密集型網站,這些選項可能有助於顯著加快渲染時間。
除非他們沒有。 例如,根據 Tim Kadlec 的說法,“AMP 文檔往往比其對應文檔更快,但它們並不一定意味著頁面是高性能的。從性能角度來看,AMP 並不是最大的區別。”
網站所有者的好處是顯而易見的:這些格式在各自平台上的可發現性以及在搜索引擎中的可見性增加。
好吧,至少以前是這樣的。 由於 AMP 不再是Top Stories的要求,出版商可能會從 AMP 轉向傳統堆棧(謝謝,Barry! )。
不過,您也可以通過重用 AMP 作為 PWA 的數據源來構建漸進式 Web AMP。 缺點? 顯然,在圍牆花園中的存在使開發人員能夠製作和維護其內容的單獨版本,並且在沒有實際 URL 的 Instant Articles 和 Apple News 的情況下(感謝 Addy,Jeremy!) 。
- 明智地選擇您的 CDN。
如上所述,根據您擁有多少動態數據,您可能能夠將部分內容“外包”給靜態站點生成器,將其推送到 CDN 並從中提供靜態版本,從而避免向服務器。 事實上,其中一些生成器實際上是網站編譯器,提供了許多開箱即用的自動優化。 隨著編譯器隨著時間的推移添加優化,編譯的輸出隨著時間的推移變得越來越小。請注意,CDN 也可以提供(和卸載)動態內容。 因此,沒有必要將您的 CDN 限制為靜態資產。 仔細檢查您的 CDN 是否執行壓縮和轉換(例如圖像優化和邊緣調整大小),它們是否為服務器工作者、A/B 測試以及邊緣端包含提供支持,這些包含組合頁面的靜態和動態部分在 CDN 的邊緣(即離用戶最近的服務器)和其他任務。 另外,檢查您的 CDN 是否支持 HTTP over QUIC (HTTP/3)。
Katie Hempenius 撰寫了一份精彩的 CDN 指南,其中提供了有關如何選擇好的 CDN 、如何對其進行微調以及評估 CDN 時要記住的所有小事的見解。 通常,最好盡可能積極地緩存內容並啟用 CDN 性能功能,例如 Brotli、TLS 1.3、HTTP/2 和 HTTP/3。
注意:根據 Patrick Meenan 和 Andy Davies 的研究,HTTP/2 優先級在許多 CDN 上被有效破壞,因此在選擇 CDN 時要小心。 Patrick 在他關於 HTTP/2 Prioritization 的演講中有更多細節(謝謝,Barry! )。
選擇 CDN 時,您可以使用這些比較網站並詳細了解其功能:
- CDN 比較,用於 Cloudfront、Aazure、KeyCDN、Fastly、Verizon、Stackpach、Akamai 等的 CDN 比較矩陣。
- CDN Perf 通過每天收集和分析 3 億個測試來衡量 CDN 的查詢速度,所有結果均基於來自全球用戶的 RUM 數據。 另請檢查 DNS 性能比較和雲性能比較。
- CDN Planet Guides 概述了特定主題的 CDN,例如 Serve Stale、Purge、Origin Shield、Prefetch 和 Compression。
- Web Almanac:CDN 採用和使用提供有關頂級 CDN 提供商、他們的 RTT 和 TLS 管理、TLS 協商時間、HTTP/2 採用等的見解。 (不幸的是,數據僅來自 2019 年)。
資產優化
- 使用 Brotli 進行純文本壓縮。
2015 年,谷歌推出了 Brotli,這是一種新的開源無損數據格式,現在所有現代瀏覽器都支持這種格式。 為 Brotli 實現編碼器和解碼器的開源 Brotli 庫具有 11 個預定義的編碼器質量級別,更高的質量級別需要更多的 CPU 以換取更好的壓縮比。 較慢的壓縮最終會導致更高的壓縮率,但 Brotli 仍然可以快速解壓縮。 值得注意的是,壓縮級別為 4 的 Brotli 比 Gzip 更小且壓縮速度更快。在實踐中,Brotli 似乎比 Gzip 更有效。 意見和經驗各不相同,但如果您的網站已經使用 Gzip 進行了優化,您可能會期望在大小縮減和 FCP 時間方面至少有一位數的改進,最多也有兩位數的改進。 您還可以估計您的站點的 Brotli 壓縮節省。
只有當用戶通過 HTTPS 訪問網站時,瀏覽器才會接受 Brotli。 Brotli 受到廣泛支持,許多 CDN 都支持它(Akamai、Netlify Edge、AWS、KeyCDN、Fastly(目前僅作為直通)、Cloudflare、CDN77),即使在尚不支持的 CDN 上也可以啟用 Brotli (與服務人員)。
問題是,由於使用 Brotli 以高壓縮級別壓縮所有資產的成本很高,因此許多託管服務提供商無法全面使用它,因為它會產生巨大的成本開銷。 事實上,在最高級別的壓縮下,Brotli非常慢,以至於服務器在等待動態壓縮資產時開始發送響應所需的時間可能會抵消文件大小的任何潛在收益。 (但如果您在構建期間有時間使用靜態壓縮,當然,更高的壓縮設置是首選。)
不過,這可能正在改變。 Brotli 文件格式包括一個內置的靜態字典,除了包含多種語言的各種字符串外,它還支持對這些單詞應用多種轉換的選項,增加了它的多功能性。 在他的研究中,Felix Hanau 發現了一種改進 5 到 9 級壓縮的方法,方法是使用“比默認字典更專業的子集”並依靠
Content-Type
標頭告訴壓縮器是否應該使用HTML、JavaScript 或 CSS 的字典。 結果是“在使用有限的字典使用方法以高壓縮級別壓縮 Web 內容時,性能影響可以忽略不計(CPU 比正常情況下的 12% 多 1% 到 3%)。”最重要的是,通過 Elena Kirilenko 的研究,我們可以使用以前的壓縮工件實現快速高效的 Brotli 再壓縮。 根據 Elena 的說法,“一旦我們通過 Brotli 壓縮了資產,並且我們嘗試動態壓縮動態內容,其中的內容類似於我們提前可用的內容,我們就可以顯著縮短壓縮時間。 "
這種情況多久發生一次? 例如,交付JavaScript 捆綁包子集(例如,當部分代碼已經緩存在客戶端或使用 WebBundles 提供動態捆綁包時)。 或者使用基於預先知道的模板的動態 HTML,或者動態子集的 WOFF2 字體。 根據 Elena 的說法,當刪除 10% 的內容時,我們可以獲得 5.3% 的壓縮改進和 39% 的壓縮速度改進,當刪除 50% 的內容時,壓縮率提高了 3.2%,壓縮速度提高了 26%。
Brotli 壓縮越來越好,所以如果你能繞過動態壓縮靜態資產的成本,那絕對是值得的。 不用說,Brotli 可以用於任何純文本負載——HTML、CSS、SVG、JavaScript、JSON 等等。
注意:截至 2021 年初,大約 60% 的 HTTP 響應是在沒有基於文本的壓縮的情況下交付的,其中 30.82% 使用 Gzip 壓縮,9.1% 使用 Brotli 壓縮(無論是在移動設備上還是在桌面上)。 例如,23.4% 的 Angular 頁面沒有被壓縮(通過 gzip 或 Brotli)。 然而,通常打開壓縮是通過簡單的開關翻轉來提高性能的最簡單的方法之一。
策略? 在最高級別使用 Brotli+Gzip 預壓縮靜態資產,並在級別 4-6 使用 Brotli 動態壓縮(動態)HTML。 確保服務器正確處理 Brotli 或 Gzip 的內容協商。
- 我們是否使用自適應媒體加載和客戶端提示?
它來自舊新聞的土地,但它始終是一個很好的提醒,使用帶有srcset
、sizes
和<picture>
元素的響應式圖像。 特別是對於媒體佔用量大的網站,我們可以通過自適應媒體加載(在本例中為 React + Next.js)更進一步,為慢速網絡和低內存設備提供輕量體驗,為快速網絡和高內存設備提供完整體驗-內存設備。 在 React 的上下文中,我們可以通過服務器上的客戶端提示和客戶端上的 react-adaptive-hooks 來實現它。隨著客戶提示的廣泛採用,響應式圖像的未來可能會發生巨大變化。 客戶端提示是 HTTP 請求標頭字段,例如
DPR
、Viewport-Width
、Width
、Save-Data
、Accept
(指定圖像格式首選項)等。 他們應該通知服務器有關用戶瀏覽器、屏幕、連接等的細節。因此,服務器可以決定如何用適當大小的圖像填充佈局,並僅以所需格式提供這些圖像。 通過客戶端提示,我們將資源選擇從 HTML 標記移到客戶端和服務器之間的請求-響應協商中。
正如 Ilya Grigorik 不久前指出的那樣,客戶提示完成了圖片 - 它們不是響應式圖像的替代品。 “
<picture>
元素在 HTML 標記中提供了必要的藝術方向控制。客戶端提示為生成的圖像請求提供註釋,從而實現資源選擇自動化。Service Worker 在客戶端提供完整的請求和響應管理功能。”例如,服務工作者可以將新的客戶端提示標頭值附加到請求中,重寫 URL 並將圖像請求指向 CDN,根據連接性和用戶偏好調整響應等。它不僅適用於圖像資產,而且適用於幾乎所有其他請求也是如此。
對於支持客戶端提示的客戶端,可以測量到圖像節省 42% 的字節和 70th+ 百分位數的 1MB+ 更少的字節。 在 Smashing Magazine 上,我們也可以測量到 19-32% 的改進。 基於 Chromium 的瀏覽器支持客戶端提示,但在 Firefox 中仍在考慮中。
但是,如果您同時為客戶端提示提供正常的響應式圖像標記和
<meta>
標記,則支持的瀏覽器將評估響應式圖像標記並使用客戶端提示 HTTP 標頭請求適當的圖像源。 - 我們是否使用響應式圖像作為背景圖像?
我們當然應該! 使用image-set
,現在 Safari 14 和除 Firefox 之外的大多數現代瀏覽器都支持,我們也可以提供響應式背景圖像:background-image: url("fallback.jpg"); background-image: image-set( "photo-small.jpg" 1x, "photo-large.jpg" 2x, "photo-print.jpg" 600dpi);
基本上,我們可以有條件地提供具有
1x
描述符的低分辨率背景圖像,以及具有2x
描述符的高分辨率圖像,甚至是具有600dpi
描述符的打印質量圖像。 但請注意:瀏覽器不會為輔助技術提供有關背景圖像的任何特殊信息,因此理想情況下,這些照片只是裝飾。 - 我們使用 WebP 嗎?
圖像壓縮通常被認為是一種快速的勝利,但在實踐中它仍然沒有得到充分利用。 當然,圖像不會阻塞渲染,但它們會嚴重影響 LCP 分數,而且通常它們對於正在使用它們的設備來說太重太大了。所以至少,我們可以探索為我們的圖像使用 WebP 格式。 事實上,隨著 Apple 在 Safari 14 中增加對 WebP 的支持,WebP 傳奇已接近尾聲。因此,經過多年的討論和辯論,截至今天,所有現代瀏覽器都支持 WebP。 因此,如果需要(請參閱 Andreas Bovens 的代碼片段)或使用內容協商(使用
Accept
標頭),我們可以使用<picture>
元素和 JPEG 後備來提供 WebP 圖像。不過,WebP 並非沒有缺點。 雖然 WebP 圖像文件的大小與等效的 Guetzli 和 Zopfli 相比,但該格式不支持像 JPEG 這樣的漸進式渲染,這就是為什麼用戶可以使用良好的 JPEG 更快地看到完成的圖像,儘管 WebP 圖像可能會通過網絡變得更快。 使用 JPEG,我們可以用一半甚至四分之一的數據提供“體面”的用戶體驗,然後再加載其餘的數據,而不是像 WebP 那樣使用半空圖像。
您的決定將取決於您追求的目標:使用 WebP,您將減少有效負載,使用 JPEG,您將提高感知性能。 您可以在 Google 的 Pascal Massimino 的 WebP Rewind 演講中了解有關 WebP 的更多信息。
要轉換為 WebP,您可以使用 WebP Converter、cwebp 或 libwebp。 Ire Aderinokun 也有一個非常詳細的關於將圖像轉換為 WebP 的教程——Josh Comeau 在他關於擁抱現代圖像格式的文章中也是如此。
Sketch 原生支持 WebP,並且可以使用 Photoshop 的 WebP 插件從 Photoshop 中導出 WebP 圖像。 但也有其他選擇。
如果您使用的是 WordPress 或 Joomla,有一些擴展可以幫助您輕鬆實現對 WebP 的支持,例如 Optimus 和 WordPress 的 Cache Enabler 以及 Joomla 自己支持的擴展(通過 Cody Arsenault)。 您還可以使用 React、樣式化組件或 gatsby-image 抽像出
<picture>
元素。啊——不要臉的插頭! — Jeremy Wagner 甚至出版了一本關於 WebP 的 Smashing 書,您可能想看看您是否對 WebP 周圍的一切感興趣。
- 我們使用 AVIF 嗎?
您可能已經聽說了一個重大消息:AVIF 已經登陸。 它是一種源自 AV1 視頻關鍵幀的新圖像格式。 它是一種開放、免版稅的格式,支持有損和無損壓縮、動畫、有損 Alpha 通道,並且可以處理銳利的線條和純色(這是 JPEG 的一個問題),同時提供更好的結果。事實上,與 WebP 和 JPEG 相比, AVIF 的性能要好得多,在相同的 DSSIM 下(使用近似人類視覺的算法,兩個或多個圖像之間的(不)相似性),文件大小中值節省高達 50%。 事實上,在他關於優化圖像加載的詳盡文章中,Malte Ubl 指出,AVIF“在一個非常重要的方面始終優於 JPEG。這與 WebP 不同,WebP 並不總是產生比 JPEG 更小的圖像,實際上可能是一個網絡 -由於缺乏對漸進式加載的支持而導致的損失。”
具有諷刺意味的是,AVIF 的性能甚至比大型 SVG 還要好,儘管它當然不應被視為 SVG 的替代品。 它也是最早支持 HDR 顏色支持的圖像格式之一; 提供更高的亮度、色位深度和色域。 唯一的缺點是目前 AVIF 不支持漸進式圖像解碼(還沒有?),與 Brotli 類似,高壓縮率編碼目前相當慢,儘管解碼速度很快。
AVIF 目前在 Chrome、Firefox 和 Opera 中得到支持,而對 Safari 的支持預計很快就會到來(因為 Apple 是創建 AV1 的小組的成員)。
那麼,如今提供圖像的最佳方式是什麼? 對於插圖和矢量圖,(壓縮的)SVG 無疑是最佳選擇。 對於照片,我們使用帶有
picture
元素的內容協商方法。 如果支持 AVIF,我們發送 AVIF 圖像; 如果不是這樣,我們首先回退到 WebP,如果 WebP 也不支持,我們切換到 JPEG 或 PNG 作為後備(如果需要,應用@media
條件):<picture> <source type="image/avif"> <source type="image/webp"> <img src="image.jpg" alt="Photo" width="450" height="350"> </picture>
坦率地說,我們更有可能在
picture
元素中使用一些條件:<picture> <source type="image/avif" /> <source type="image/webp" /> <source type="image/jpeg" /> <img src="fallback-image.jpg" alt="Photo" width="450" height="350"> </picture>
<picture> <source type="image/avif" /> <source type="image/webp" /> <source type="image/jpeg" /> <img src="fallback-image.jpg" alt="Photo" width="450" height="350"> </picture>
對於選擇使用
prefers-reduced-motion
運動的客戶,您可以通過將動畫圖像與靜態圖像交換更進一步:<picture> <source media="(prefers-reduced-motion: reduce)" type="image/avif"></source> <source media="(prefers-reduced-motion: reduce)" type="image/jpeg"></source> <source type="image/avif"></source> <img src="motion.jpg" alt="Animated AVIF"> </picture>
<picture> <source media="(prefers-reduced-motion: reduce)" type="image/avif"></source> <source media="(prefers-reduced-motion: reduce)" type="image/jpeg"></source> <source type="image/avif"></source> <img src="motion.jpg" alt="Animated AVIF"> </picture>
在過去的幾個月裡,AVIF 獲得了相當大的關注:
- 我們可以在 DevTools 的 Rendering 面板中測試 WebP/AVIF 回退。
- 我們可以使用 Squoosh、AVIF.io 和 libavif 對 AVIF 文件進行編碼、解碼、壓縮和轉換。
- 我們可以使用 Jake Archibald 的 AVIF Preact 組件,它在 worker 中解碼 AVIF 文件並將結果顯示在畫布上,
- 為了只向支持的瀏覽器提供 AVIF,我們可以使用 PostCSS 插件和 315B 腳本在您的 CSS 聲明中使用 AVIF。
- 我們可以使用 CSS 和 Cloudlare Workers 逐步交付新的圖像格式,以動態更改返回的 HTML 文檔,從
accept
頭推斷信息,然後根據需要添加webp/avif
等類。 - AVIF 已經在 Cloudinary 中可用(有使用限制),Cloudflare 在 Image Resizing 中支持 AVIF,您可以在 Netlify 中啟用帶有自定義 AVIF 標頭的 AVIF。
- 在動畫方面,AVIF 的表現與 Safari 的
<img src=mp4>
一樣好,總體上優於 GIF 和 WebP,但 MP4 的表現仍然更好。 - 一般來說,對於動畫,AVC1 (h264) > HVC1 > WebP > AVIF > GIF,假設基於 Chromium 的瀏覽器將永遠支持
<img src=mp4>
。 - 您可以在 Netflix 的 Aditya Mavlankar 的 AVIF for Next Generation Image Coding 演講和 Cloudflare 的 Kornel Lesinski 的 AVIF 圖像格式演講中找到有關 AVIF 的更多詳細信息。
- AVIF 的所有內容的絕佳參考:Jake Archibald 關於 AVIF 的綜合帖子已登陸。
那麼未來的AVIF呢? Jon Sneyers 不同意:AVIF 的性能比 JPEG XL 差 60%,JPEG XL 是另一種由 Google 和 Cloudinary 開發的免費開放格式。 事實上,JPEG XL 似乎全面表現得更好。 但是,JPEG XL 仍處於標準化的最後階段,還不能在任何瀏覽器中工作。 (不要與來自優秀的 Internet Explorer 9 次的 Microsoft JPEG-XR 混淆)。
- JPEG/PNG/SVG 是否正確優化?
當您在著陸頁上工作時,英雄圖像的加載速度至關重要編碼器專注於感知性能,並利用 Zopfli 和 WebP 的學習成果。 唯一的缺點:處理時間慢(每百萬像素 CPU 需要一分鐘)。對於 PNG,我們可以使用 Pingo,對於 SVG,我們可以使用 SVGO 或 SVGOMG。 如果您需要從網站快速預覽、複製或下載所有 SVG 資源,svg-grabber 也可以為您完成。
每一篇圖像優化文章都會說明這一點,但保持矢量資源的干淨和緊湊總是值得一提的。 確保清理未使用的資產,刪除不必要的元數據並減少藝術品中的路徑點數量(以及 SVG 代碼)。 (謝謝,傑里米! )
不過,也有一些有用的在線工具可用:
- 使用 Squoosh 以最佳壓縮級別(有損或無損)壓縮、調整大小和處理圖像,
- 使用 Guetzli.it 通過 Guetzli 壓縮和優化 JPEG 圖像,它適用於具有銳利邊緣和純色的圖像(但可能會慢一些)。
- 使用響應式圖像斷點生成器或 Cloudinary 或 Imgix 等服務來自動優化圖像。 此外,在許多情況下,單獨使用
srcset
和sizes
將獲得顯著的好處。 - 要檢查響應式標記的效率,您可以使用imaging-heap,這是一個命令行工具,可以測量視口大小和設備像素比的效率。
- 您可以將自動圖像壓縮添加到您的 GitHub 工作流程中,因此沒有圖像可以在未壓縮的情況下投入生產。 該操作使用可處理 PNG 和 JPG 的 mozjpeg 和 libvips。
- 為了優化存儲,您可以使用 Dropbox 的新 Lepton 格式將 JPEG 平均壓縮 22%。
- 如果您想儘早顯示佔位符圖像,請使用 BlurHash。 BlurHash 拍攝一張圖片,並為您提供一個短字符串(僅 20-30 個字符!),代表該圖片的佔位符。 該字符串足夠短,可以很容易地作為字段添加到 JSON 對像中。
有時僅優化圖像並不能解決問題。 為了縮短開始渲染關鍵圖像所需的時間,延遲加載不太重要的圖像並延遲任何腳本在關鍵圖像已經渲染後加載。 最安全的方法是混合延遲加載,當我們使用本機延遲加載和延遲加載時,該庫可以檢測通過用戶交互觸發的任何可見性更改(使用我們稍後將探討的 IntersectionObserver)。 此外:
- 考慮預加載關鍵圖像,這樣瀏覽器就不會太晚發現它們。 對於背景圖片,如果你想更加激進,可以使用
<img src>
將圖片添加為普通圖片,然後將其隱藏在屏幕之外。 - 考慮通過根據媒體查詢指定不同的圖像顯示尺寸來使用 Sizes 屬性交換圖像,例如,操縱
sizes
以交換放大鏡組件中的源。 - 查看圖像下載不一致的情況,以防止前景和背景圖像的意外下載。 注意默認加載但可能永遠不會顯示的圖像——例如在輪播、手風琴和圖像畫廊中。
- 確保始終在圖像上設置
width
和height
。 注意 CSS 中的aspect-ratio
屬性和intrinsicsize
屬性,這將允許我們設置圖像的寬高比和尺寸,因此瀏覽器可以提前預留一個預定義的佈局槽以避免頁面加載期間的佈局跳轉。
如果您喜歡冒險,您可以使用 Edge 工作程序(基本上是位於 CDN 上的實時過濾器)來切分和重新排列 HTTP/2 流,以通過網絡更快地發送圖像。 邊緣工作者使用 JavaScript 流,這些流使用您可以控制的塊(基本上它們是在 CDN 邊緣上運行的 JavaScript,可以修改流響應),因此您可以控製圖像的交付。
使用服務人員,為時已晚,因為您無法控制線路上的內容,但它確實適用於 Edge 工作者。 因此,您可以在為特定登錄頁面逐步保存的靜態 JPEG 之上使用它們。
還不夠好? 好吧,您還可以使用多背景圖像技術提高圖像的感知性能。 請記住,使用對比度和模糊不必要的細節(或去除顏色)也可以減小文件大小。 啊,你需要放大一張小照片而不損失質量嗎? 考慮使用 Letsenhance.io。
到目前為止,這些優化只涵蓋了基礎知識。 Addy Osmani 發布了一份關於基本圖像優化的非常詳細的指南,該指南非常深入地介紹了圖像壓縮和色彩管理的細節。 例如,您可以模糊圖像的不必要部分(通過對其應用高斯模糊濾鏡)以減小文件大小,最終您甚至可能開始去除顏色或將圖片變為黑白以進一步減小大小. 對於背景圖像,從 Photoshop 中以 0 到 10% 的質量導出照片也是完全可以接受的。
在 Smashing Magazine 上,我們使用後綴
-opt
作為圖像名稱 - 例如,brotli-compression-opt.png
; 每當圖像包含該後綴時,團隊中的每個人都知道該圖像已被優化。啊,不要在網絡上使用 JPEG-XR ——“在 CPU 上解碼 JPEG-XRs 軟件端的處理抵消甚至超過了字節大小節省的潛在積極影響,尤其是在 SPA 的上下文中”(不是與 Cloudinary/Google 的 JPEG XL 混合)。
- 視頻是否正確優化?
到目前為止,我們涵蓋了圖像,但我們避免了關於好的 ol' GIF 的對話。 儘管我們喜歡 GIF,但現在是時候徹底放棄它們了(至少在我們的網站和應用程序中)。 與其加載影響渲染性能和帶寬的繁重的動畫 GIF,不如切換到動畫 WebP(GIF 作為後備)或完全用循環的 HTML5 視頻替換它們。與圖像不同,瀏覽器不會預加載
<video>
內容,但 HTML5 視頻往往比 GIF 更輕更小。 不是一個選項? 好吧,至少我們可以使用有損 GIF、gifsicle 或 giflossy 為 GIF 添加有損壓縮。Colin Bendell 的測試表明,Safari 技術預覽中
img
標籤內的內嵌視頻顯示速度至少比 GIF 快 20 倍,解碼速度快 7 倍,而且文件大小只是一小部分。 但是,其他瀏覽器不支持它。在好消息的土地上,視頻格式多年來一直在大規模發展。 很長一段時間以來,我們一直希望 WebM 能夠成為統治一切的格式,而 WebP(基本上是 WebM 視頻容器內的一張靜止圖像)將成為過時的圖像格式的替代品。 的確,Safari 現在支持 WebP,但儘管 WebP 和 WebM 近來獲得了支持,但這一突破並沒有真正發生。
儘管如此,我們仍然可以將 WebM 用於大多數現代瀏覽器:
<!-- By Houssein Djirdeh. https://web.dev/replace-gifs-with-videos/ --> <!-- A common scenartio: MP4 with a WEBM fallback. --> <video autoplay loop muted playsinline> <source src="my-animation.webm" type="video/webm"> <source src="my-animation.mp4" type="video/mp4"> </video>
但也許我們可以完全重新審視它。 2018 年,開放媒體聯盟發布了一種新的有前途的視頻格式,稱為AV1 。 AV1 具有類似於 H.265 編解碼器(H.264 的演變)的壓縮,但與後者不同的是,AV1 是免費的。 H.265 許可證定價促使瀏覽器供應商改用性能相當的 AV1: AV1(就像 H.265)的壓縮率是 WebM 的兩倍。
事實上,蘋果目前使用的是 HEIF 格式和 HEVC(H.265),而最新 iOS 上的所有照片和視頻都是以這些格式保存的,而不是 JPEG。 雖然 HEIF 和 HEVC (H.265) 還沒有正確地暴露在網絡上(還沒有?),但 AV1 是——而且它正在獲得瀏覽器的支持。 因此,在您的
<video>
標籤中添加AV1
源代碼是合理的,因為所有瀏覽器供應商似乎都參與其中。目前,最廣泛使用和支持的編碼是 H.264,由 MP4 文件提供,因此在提供文件之前,請確保您的 MP4 使用多通道編碼進行處理,使用 frei0r iirblur 效果(如果適用)進行模糊處理,並且moov atom 元數據被移動到文件的頭部,而您的服務器接受字節服務。 Boris Schapira 為 FFmpeg 提供了精確的指令以最大限度地優化視頻。 當然,提供 WebM 格式作為替代方案也會有所幫助。
需要更快地開始渲染視頻但視頻文件仍然太大? 例如,每當您在登錄頁面上有大型背景視頻時? 一種常用的技術是首先將第一幀顯示為靜止圖像,或者顯示一個經過高度優化的短循環片段,可以解釋為視頻的一部分,然後,只要視頻緩衝足夠,就開始播放實際的視頻。 Doug Sillars 編寫了一份詳細的背景視頻性能指南,在這種情況下可能會有所幫助。 (謝謝,Guy Podjarny! )。
對於上述場景,您可能需要提供響應式海報圖片。 默認情況下,
video
元素只允許一張圖片作為海報,這不一定是最佳的。 我們可以使用 Responsive Video Poster,這是一個 JavaScript 庫,允許您為不同的屏幕使用不同的海報圖像,同時還添加過渡疊加和視頻佔位符的完整樣式控制。研究表明,視頻流質量會影響觀看者的行為。 事實上,如果啟動延遲超過 2 秒左右,觀眾就會開始放棄視頻。 超過這一點,延遲增加 1 秒會導致放棄率增加大約 5.8%。 因此,視頻開始時間的中位數為 12.8 秒也就不足為奇了,其中 40% 的視頻至少有 1 個停頓,20% 的視頻至少有 2 秒的停頓視頻播放。 事實上,在 3G 上,視頻停頓是不可避免的,因為視頻播放速度快於網絡提供的內容。
那麼,解決方案是什麼? 通常小屏幕設備無法處理我們為桌面服務的 720p 和 1080p。 根據 Doug Sillars 的說法,我們可以創建更小的視頻版本,並使用 Javascript 來檢測小屏幕的來源,以確保在這些設備上快速流暢地播放。 或者,我們可以使用流式視頻。 HLS 視頻流將向設備提供適當大小的視頻——抽像出為不同屏幕創建不同視頻的需要。 它還將協商網絡速度,並根據您使用的網絡速度調整視頻比特率。
為了避免帶寬浪費,我們只能為實際可以播放視頻的設備添加視頻源。 或者,我們可以完全從
video
標籤中刪除autoplay
屬性,並使用 JavaScript 為更大的屏幕插入autoplay
。 此外,我們需要在video
上添加preload="none"
來告訴瀏覽器在它真正需要文件之前不要下載任何視頻文件:<!-- Based on Doug Sillars's post. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ --> <video preload="none" playsinline muted loop width="1920" height="1080" poster="poster.jpg"> <source src="video.webm" type="video/webm"> <source src="video.mp4" type="video/mp4"> </video>
然後我們可以專門針對實際支持 AV1 的瀏覽器:
<!-- Based on Doug Sillars's post. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ --> <video preload="none" playsinline muted loop width="1920" height="1080" poster="poster.jpg"> <source src="video.av1.mp4" type="video/mp4; codecs=av01.0.05M.08"> <source src="video.hevc.mp4" type="video/mp4; codecs=hevc"> <source src="video.webm" type="video/webm"> <source src="video.mp4" type="video/mp4"> </video>
然後我們可以在某個閾值(例如 1000 像素)上重新添加
autoplay
:/* By Doug Sillars. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ */ <script> window.onload = addAutoplay(); var videoLocation = document.getElementById("hero-video"); function addAutoplay() { if(window.innerWidth > 1000){ videoLocation.setAttribute("autoplay",""); }; } </script>
視頻播放性能本身就是一個故事,如果您想深入了解它的詳細信息,請查看 Doug Sillars 關於視頻當前狀態和視頻交付最佳實踐的另一個系列,其中包含有關視頻交付指標的詳細信息、視頻預加載、壓縮和流式傳輸。 最後,您可以使用 Stream 或 Not 檢查視頻流的速度或速度。
- 網絡字體交付是否優化?
值得一問的第一個問題是,我們是否可以一開始就使用 UI 系統字體——我們只需要確保仔細檢查它們在各種平台上的顯示是否正確。 如果不是這種情況,我們提供的網絡字體很可能包含字形以及未使用的額外功能和權重。 我們可以要求我們的字體鑄造廠對Web 字體進行子集化,或者如果我們使用開源字體,則使用 Glyphhanger 或 Fontsquirrel 自行對它們進行子集化。 我們甚至可以使用 Peter Muller 的子字體自動化我們的整個工作流程,這是一個命令行工具,可以靜態分析您的頁面以生成最佳的網絡字體子集,然後將它們注入我們的頁面。WOFF2 支持很棒,我們可以使用 WOFF 作為不支持它的瀏覽器的後備——或者也許可以為舊版瀏覽器提供系統字體。 Web 字體加載有很多很多的選項,我們可以從 Zach Leatherman 的“字體加載策略綜合指南”中選擇一種策略(代碼片段也可以作為 Web 字體加載食譜提供)。
今天要考慮的更好的選擇可能是帶有
preload
的關鍵 FOFT 和“妥協”方法。 他們都使用兩階段渲染來分步交付網絡字體——首先是一個小的超子集,需要使用網絡字體快速準確地渲染頁面,然後異步加載系列的其餘部分。 不同之處在於“妥協”技術僅在不支持字體加載事件時才異步加載 polyfill,因此您不需要默認加載 polyfill。 需要快速獲勝嗎? Zach Leatherman 有一個 23 分鐘的快速教程和案例研究,可以讓您的字體井井有條。一般來說,使用
preload
資源提示來預加載字體可能是一個好主意,但在您的標記中包含指向關鍵 CSS 和 JavaScript 的鏈接之後的提示。 對於preload
,存在優先級難題,因此請考慮在外部阻塞腳本之前將rel="preload"
元素注入 DOM。 根據 Andy Davies 的說法,“使用腳本注入的資源在腳本執行之前對瀏覽器是隱藏的,當瀏覽器發現preload
提示時,我們可以使用這種行為來延遲。” 否則,字體加載將在第一次渲染時花費您。有選擇性並選擇最重要的文件是個好主意,例如那些對渲染至關重要的文件或有助於避免可見和破壞性文本重排的文件。 一般來說,Zach 建議預加載每個系列的一到兩種字體——如果它們不太重要,延遲一些字體加載也是有意義的。
在
@font-face
規則中定義font-family
時,使用local()
值(按名稱引用本地字體)已變得非常普遍:/* Warning! Not a good idea! */ @font-face { font-family: Open Sans; src: local('Open Sans Regular'), local('OpenSans-Regular'), url('opensans.woff2') format ('woff2'), url('opensans.woff') format('woff'); }
這個想法是有道理的:一些流行的開源字體,例如Open Sans,會預裝一些驅動程序或應用程序,所以如果字體在本地可用,瀏覽器不需要下載網絡字體,可以顯示本地立即字體。 正如 Bram Stein 所指出的,“雖然本地字體與網絡字體的名稱匹配,但它很可能不是同一種字體。許多網絡字體與其“桌面”版本不同。文本可能呈現不同,某些字符可能會下降回到其他字體,OpenType 功能可能完全缺失,或者行高可能不同。”
此外,隨著字體隨著時間的推移而演變,本地安裝的版本可能與 Web 字體大不相同,字符看起來也大不相同。 因此,根據 Bram 的說法,最好不要在
@font-face
規則中混合本地安裝的字體和 Web 字體。 除了 Android 對 Roboto 的請求外,Google 字體也對所有用戶的 CSS 結果禁用local()
。沒有人喜歡等待內容顯示。 使用
font-display
CSS 描述符,我們可以控製字體加載行為並使內容能夠立即(使用font-display: optional
)或幾乎立即(超時 3 秒,只要字體被成功下載——使用font-display: swap
)。 (嗯,它比這更複雜一些。)但是,如果您想盡量減少文本重排的影響,我們可以使用字體加載 API (在所有現代瀏覽器中都支持)。 具體來說,這意味著對於每種字體,我們將創建一個
FontFace
對象,然後嘗試獲取所有字體,然後才將它們應用到頁面上。 這樣,我們通過異步加載所有字體來對所有重繪進行分組,然後恰好從備用字體切換到網絡字體一次。 看看 Zach 的解釋,從 32:15 開始,以及代碼片段):/* Load two web fonts using JavaScript */ /* Zach Leatherman: https://noti.st/zachleat/KNaZEg/the-five-whys-of-web-font-loading-performance#sWkN4u4 */ // Remove existing @font-face blocks // Create two let font = new FontFace("Noto Serif", /* ... */); let fontBold = new FontFace("Noto Serif, /* ... */); // Load two fonts let fonts = await Promise.all([ font.load(), fontBold.load() ]) // Group repaints and render both fonts at the same time! fonts.forEach(font => documents.fonts.add(font));
/* Load two web fonts using JavaScript */ /* Zach Leatherman: https://noti.st/zachleat/KNaZEg/the-five-whys-of-web-font-loading-performance#sWkN4u4 */ // Remove existing @font-face blocks // Create two let font = new FontFace("Noto Serif", /* ... */); let fontBold = new FontFace("Noto Serif, /* ... */); // Load two fonts let fonts = await Promise.all([ font.load(), fontBold.load() ]) // Group repaints and render both fonts at the same time! fonts.forEach(font => documents.fonts.add(font));
為了在使用 Font Loading API 的情況下儘早開始獲取字體,Adrian Bece 建議添加一個不間斷空格
nbsp;
在body
的頂部,並使用aria-visibility: hidden
和.hidden
類在視覺上隱藏它:<body class="no-js"> <!-- ... Website content ... --> <div aria-visibility="hidden" class="hidden"> <!-- There is a non-breaking space here --> </div> <script> document.getElementsByTagName("body")[0].classList.remove("no-js"); </script> </body>
<body class="no-js"> <!-- ... Website content ... --> <div aria-visibility="hidden" class="hidden"> <!-- There is a non-breaking space here --> </div> <script> document.getElementsByTagName("body")[0].classList.remove("no-js"); </script> </body>
這與為不同加載狀態聲明不同字體系列的 CSS 一起使用,一旦字體成功加載,更改由 Font Loading API 觸發:
body:not(.wf-merriweather--loaded):not(.no-js) { font-family: [fallback-system-font]; /* Fallback font styles */ } .wf-merriweather--loaded, .no-js { font-family: "[web-font-name]"; /* Webfont styles */ } /* Accessible hiding */ .hidden { position: absolute; overflow: hidden; clip: rect(0 0 0 0); height: 1px; width: 1px; margin: -1px; padding: 0; border: 0; }
body:not(.wf-merriweather--loaded):not(.no-js) { font-family: [fallback-system-font]; /* Fallback font styles */ } .wf-merriweather--loaded, .no-js { font-family: "[web-font-name]"; /* Webfont styles */ } /* Accessible hiding */ .hidden { position: absolute; overflow: hidden; clip: rect(0 0 0 0); height: 1px; width: 1px; margin: -1px; padding: 0; border: 0; }
如果您想知道為什麼儘管進行了所有優化,Lighthouse 仍然建議消除渲染阻塞資源(字體),在同一篇文章中,Adrian Bece 提供了一些讓 Lighthouse 滿意的技術,以及 Gatsby Omni Font Loader,一種高性能異步字體Gatsby 的加載和 Flash Of Unstyled Text (FOUT) 處理插件。
現在,我們中的許多人可能正在使用 CDN 或第三方主機來加載 Web 字體。 一般來說,如果可以的話,自託管所有靜態資產總是更好,因此請考慮使用 google-webfonts-helper,這是一種自託管 Google 字體的輕鬆方式。 如果不可能,您也許可以通過頁面來源代理 Google 字體文件。
值得注意的是,雖然 Google 做了很多開箱即用的工作,所以服務器可能需要一些調整以避免延遲(謝謝,巴里! )
這一點非常重要,尤其是自 Chrome v86(2020 年 10 月發布)以來,由於瀏覽器緩存分區,字體等跨站點資源無法再在同一個 CDN 上共享。 這種行為多年來一直是 Safari 的默認行為。
但是,如果根本不可能,有一種方法可以使用 Harry Roberts 的代碼段獲得最快的 Google 字體:
<!-- By Harry Roberts. https://csswizardry.com/2020/05/the-fastest-google-fonts/ - 1. Preemptively warm up the fonts' origin. - 2. Initiate a high-priority, asynchronous fetch for the CSS file. Works in - most modern browsers. - 3. Initiate a low-priority, asynchronous fetch that gets applied to the page - only after it's arrived. Works in all browsers with JavaScript enabled. - 4. In the unlikely event that a visitor has intentionally disabled - JavaScript, fall back to the original method. The good news is that, - although this is a render-blocking request, it can still make use of the - preconnect which makes it marginally faster than the default. --> <!-- [1] --> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <!-- [2] --> <link rel="preload" as="style" href="$CSS&display=swap" /> <!-- [3] --> <link rel="stylesheet" href="$CSS&display=swap" media="print" onload="this.media='all'" /> <!-- [4] --> <noscript> <link rel="stylesheet" href="$CSS&display=swap" /> </noscript>
Harry 的策略是先發製人地預熱字體的起源。 然後我們為 CSS 文件啟動一個高優先級的異步獲取。 之後,我們啟動一個低優先級的異步獲取,該獲取僅在頁面到達後才應用於頁面(使用打印樣式表技巧)。 最後,如果不支持 JavaScript,我們將退回到原始方法。
啊,說到谷歌字體:你可以通過
&text
只聲明你需要的字符來減少谷歌字體請求的90% 的大小。 另外,最近谷歌字體也增加了對字體顯示的支持,所以我們可以直接使用它。不過要注意一點。 如果您使用
font-display: optional
,那麼使用preload
可能不是最佳選擇,因為它會提前觸發該 Web 字體請求(如果您有其他需要獲取的關鍵路徑資源,則會導致網絡擁塞)。 使用preconnect
來加快跨域字體請求,但要小心preload
,因為從不同來源預加載字體會導致網絡爭用。 所有這些技術都包含在 Zach 的 Web 字體加載配方中。另一方面,如果用戶在可訪問性首選項中啟用了減少運動或選擇了數據保護模式(請參閱
Save-Data
標題),則選擇退出網絡字體(或至少第二階段渲染)可能是個好主意,或者當用戶連接速度較慢時(通過網絡信息 API)。如果用戶選擇了數據保存模式(還有其他用例),我們還可以使用
prefers-reduced-data
CSS 媒體查詢來不定義字體聲明。 如果來自客戶端提示 HTTP 擴展的Save-Data
請求標頭打開/關閉以允許與 CSS 一起使用,則媒體查詢基本上會公開。 目前僅支持在 Chrome 和 Edge 後面的標誌。指標? 要衡量 Web 字體加載性能,請考慮All Text Visible指標(所有字體已加載且所有內容以 Web 字體顯示的時刻)、Time to Real Italics 以及首次渲染後的Web Font Reflow Count 。 顯然,這兩個指標越低,性能越好。
你可能會問,可變字體呢? 重要的是要注意可變字體可能需要重要的性能考慮。 它們為我們提供了更廣泛的印刷選擇設計空間,但它的代價是單個串行請求而不是多個單獨的文件請求。
雖然可變字體大大減少了字體文件的整體組合文件大小,但該單個請求可能會很慢,從而阻止頁面上所有內容的呈現。 所以子集化和將字體分割成字符集仍然很重要。 不過好的一面是,使用可變字體,默認情況下我們會得到一個重排,因此不需要 JavaScript 來對重繪進行分組。
現在,怎樣才能製定防彈的網絡字體加載策略呢? 子集字體並為 2-stage-render 準備它們,使用字體
font-display
描述符聲明它們,使用 Font Loading API 對重繪進行分組並將字體存儲在持久服務工作者的緩存中。 在第一次訪問時,在阻塞外部腳本之前註入腳本的預加載。 如有必要,您可以使用 Bram Stein 的 Font Face Observer。 如果您對測量字體加載的性能感興趣,Andreas Marschke 將探索使用 Font API 和 UserTiming API 進行的性能跟踪。最後,不要忘記包含
unicode-range
以將大字體分解為較小的特定語言字體,並使用 Monica Dinculescu 的字體樣式匹配器來最小化佈局中的不和諧變化,因為後備和網頁字體。或者,要為備用字體模擬 Web 字體,我們可以使用 @font-face 描述符來覆蓋字體指標(演示,在 Chrome 87 中啟用)。 (請注意,調整會因複雜的字體堆棧而變得複雜。)
未來看起來光明嗎? 通過漸進式字體豐富,最終我們可能能夠“在任何給定頁面上僅下載所需的字體部分,並且對於該字體的後續請求,可以根據後續頁面的要求使用額外的字形集動態地‘修補’原始下載意見”,正如 Jason Pamental 解釋的那樣。 增量傳輸演示已經可用,並且正在進行中。
構建優化
- 我們是否確定了我們的優先事項?
最好先知道你在處理什麼。 對您的所有資產(JavaScript、圖像、字體、第三方腳本和頁面上的“昂貴”模塊,例如輪播、複雜的信息圖表和多媒體內容)進行清點,並將它們分組分解。設置電子表格。 定義舊版瀏覽器的基本核心體驗(即完全可訪問的核心內容)、功能強大的瀏覽器的增強體驗(即豐富、完整的體驗)和附加功能(非絕對必需且可以延遲加載的資產,例如網絡字體、不必要的樣式、輪播腳本、視頻播放器、社交媒體小部件、大圖像)。 多年前,我們發表了一篇關於“提高 Smashing Magazine 的性能”的文章,詳細描述了這種方法。
在優化性能時,我們需要反映我們的優先事項。 立即加載核心體驗,然後是增強功能,然後是附加功能。
- 你在生產中使用原生 JavaScript 模塊嗎?
還記得將核心體驗發送到舊版瀏覽器並增強現代瀏覽器體驗的出色技巧嗎? 該技術的更新變體可以使用 ES2017+<script type="module">
,也稱為模塊/無模塊模式(也由 Jeremy Wagner 作為差分服務引入)。這個想法是編譯並提供兩個單獨的 JavaScript 包:“常規”構建,一個帶有 Babel-transforms 和 polyfills 並僅將它們提供給真正需要它們的舊版瀏覽器,另一個包(相同功能)沒有轉換或填充物。
因此,我們通過減少瀏覽器需要處理的腳本數量來幫助減少主線程的阻塞。 Jeremy Wagner 發表了一篇關於差異服務以及如何在構建管道中設置它的綜合文章,從設置 Babel 到您需要在 Webpack 中進行哪些調整,以及完成所有這些工作的好處。
默認情況下,原生 JavaScript 模塊腳本被延遲,因此在進行 HTML 解析時,瀏覽器將下載主模塊。
不過需要注意的一點是: module/nomodule 模式在某些客戶端上可能會適得其反,因此您可能需要考慮一種解決方法:Jeremy 風險較小的差異服務模式,然而,它避開了預加載掃描器,這可能會以一種可能不會影響性能的方式預料。 (謝謝,傑里米! )
實際上,Rollup 支持將模塊作為輸出格式,因此我們既可以捆綁代碼,也可以在生產環境中部署模塊。 Parcel 在 Parcel 2 中具有模塊支持。對於 Webpack,module-nomodule-plugin 自動生成模塊/nomodule 腳本。
注意:值得一提的是,僅憑功能檢測不足以對發送到該瀏覽器的有效負載做出明智的決定。 就其本身而言,我們無法從瀏覽器版本推斷設備能力。 例如,發展中國家的廉價 Android 手機大多運行 Chrome,儘管內存和 CPU 功能有限,但仍會減少芥末味。
最終,使用設備內存客戶端提示頭,我們將能夠更可靠地定位低端設備。 在撰寫本文時,僅在 Blink 中支持標頭(通常用於客戶端提示)。 由於設備內存也有一個在 Chrome 中可用的 JavaScript API,一個選項可能是基於 API 進行功能檢測,如果不支持則回退到模塊/無模塊模式(謝謝,Yoav! )。
- 您是否在使用搖樹、範圍提升和代碼拆分?
Tree-shaking 是一種清理構建過程的方法,它只包含生產中實際使用的代碼,並消除 Webpack 中未使用的導入。 使用 Webpack 和 Rollup,我們還具有範圍提升功能,這兩種工具都可以檢測import
鏈在哪裡可以展平並轉換為一個內聯函數,而不會影響代碼。 使用 Webpack,我們也可以使用 JSON Tree Shaking。Code-splitting 是另一個 Webpack 特性,它將你的代碼庫分成按需加載的“塊”。 並非所有的 JavaScript 都必須立即下載、解析和編譯。 一旦在代碼中定義了分割點,Webpack 就可以處理依賴項和輸出文件。 它使您能夠保持較小的初始下載並在應用程序請求時按需請求代碼。 Alexander Kondrov 對使用 Webpack 和 React 進行代碼拆分進行了精彩的介紹。
考慮使用 preload-webpack-plugin 將路由代碼拆分,然後提示瀏覽器使用
<link rel="preload">
或<link rel="prefetch">
預加載它們。 Webpack 內聯指令還可以控制preload
/prefetch
。 (不過要注意優先級問題。)在哪裡定義分割點? 通過跟踪哪些 CSS/JavaScript 塊被使用,哪些未被使用。 Umar Hansa 解釋瞭如何使用 Devtools 的代碼覆蓋來實現它。
在處理單頁應用程序時,我們需要一些時間來初始化應用程序,然後才能渲染頁面。 您的設置將需要您的自定義解決方案,但您可以注意模塊和技術以加快初始渲染時間。 例如,這裡介紹瞭如何調試 React 性能並消除常見的 React 性能問題,以及如何提高 Angular 中的性能。 一般來說,大多數性能問題都來自啟動應用程序的初始時間。
那麼,什麼是積極但又不過分積極地進行代碼拆分的最佳方式呢? 根據 Phil Walton 的說法,“除了通過動態導入進行代碼拆分之外,[我們] 還可以在包級別使用代碼拆分,其中每個導入的節點模塊都會根據其包的名稱放入一個塊中。” Phil 還提供了有關如何構建它的教程。
- 我們可以改進 Webpack 的輸出嗎?
由於 Webpack 通常被認為是神秘的,因此有很多 Webpack 插件可能會派上用場,以進一步減少 Webpack 的輸出。 以下是一些可能需要更多關注的更晦澀難懂的內容。其中一個有趣的來自 Ivan Akulov 的主題。 想像一下,您有一個函數,您調用一次,將其結果存儲在一個變量中,然後不使用該變量。 搖樹將刪除變量,但不會刪除函數,因為它可能會被使用。 但是,如果該功能未在任何地方使用,您可能需要將其刪除。 為此,請在函數調用前加上
/*#__PURE__*/
,這是 Uglify 和 Terser 支持的 - 完成!以下是 Ivan 推薦的其他一些工具:
- purgecss-webpack-plugin 刪除未使用的類,尤其是在您使用 Bootstrap 或 Tailwind 時。
- 使用 split-chunks-plugin 啟用
optimization.splitChunks: 'all'
。 這將使 webpack 自動對您的條目包進行代碼拆分,以實現更好的緩存。 - 設置
optimization.runtimeChunk: true
。 這會將 webpack 的運行時移動到一個單獨的塊中——並且還會改進緩存。 - google-fonts-webpack-plugin 下載字體文件,因此您可以從您的服務器提供它們。
- workbox-webpack-plugin 允許您為所有 webpack 資產生成具有預緩存設置的服務工作者。 此外,請查看 Service Worker Packages,這是一份可以立即應用的模塊綜合指南。 或者使用 preload-webpack-plugin 為所有 JavaScript 塊生成
preload
/prefetch
。 - speed-measure-webpack-plugin 測量你的 webpack 構建速度,提供構建過程中哪些步驟最耗時的洞察。
- 當你的包包含同一個包的多個版本時,duplicate-package-checker-webpack-plugin 會發出警告。
- 使用範圍隔離並在編譯時動態縮短 CSS 類名。
- 你可以將 JavaScript 卸載到 Web Worker 中嗎?
為了減少對 Time-to-Interactive 的負面影響,考慮將繁重的 JavaScript 卸載到 Web Worker 中可能是一個好主意。隨著代碼庫的不斷增長,UI 性能瓶頸會出現,從而降低用戶體驗。 這是因為 DOM 操作在主線程上與 JavaScript 一起運行。 使用網絡工作者,我們可以將這些昂貴的操作轉移到在不同線程上運行的後台進程。 Web Worker 的典型用例是預取數據和漸進式 Web 應用程序以提前加載和存儲一些數據,以便您以後可以在需要時使用它。 您可以使用 Comlink 來簡化主頁和工作人員之間的通信。 還有一些工作要做,但我們正在到達那裡。
有一些關於 Web Worker 的有趣案例研究展示了將框架和應用程序邏輯移動到 Web Worker 的不同方法。 結論:總的來說,仍然存在一些挑戰,但已經有一些很好的用例(感謝 Ivan Akulov! )。
從 Chrome 80 開始,一種具有 JavaScript 模塊性能優勢的Web 工作者新模式已經發布,稱為模塊工作者。 我們可以更改腳本加載和執行以匹配
script type="module"
,此外,我們還可以對延遲加載代碼使用動態導入,而不會阻塞工作線程的執行。如何開始? 以下是一些值得研究的資源:
- Surma 發布了一篇關於如何在瀏覽器的主線程中運行 JavaScript 以及何時應該使用 Web Workers 的優秀指南。
- 另外,請查看 Surma 關於主線程架構的討論。
- Shubhie Panicker 和 Jason Miller 的 A Quest to Guarantee Responsiveness 提供了有關如何使用 Web 工作者以及何時避免使用它們的詳細見解。
- 擺脫用戶的方式:使用 Web Worker 減少 Jank 重點介紹了使用 Web Worker 的有用模式、worker 之間通信的有效方法、處理主線程之外的複雜數據處理以及測試和調試它們。
- Workerize 允許您將模塊移動到 Web Worker 中,自動將導出的函數反映為異步代理。
- 如果你使用 Webpack,你可以使用 workerize-loader。 或者,您也可以使用 worker-plugin。
請注意,Web Worker 無權訪問 DOM,因為 DOM 不是“線程安全的”,它們執行的代碼需要包含在單獨的文件中。
- 你可以將“熱路徑”卸載到 WebAssembly 嗎?
我們可以將繁重的計算任務轉移到 WebAssembly ( WASM ),這是一種二進制指令格式,被設計為用於編譯 C/C++/Rust 等高級語言的可移植目標。 它的瀏覽器支持非常出色,並且隨著 JavaScript 和 WASM 之間的函數調用變得越來越快,它最近變得可行。 此外,它甚至還支持 Fastly 的邊緣雲。當然,WebAssembly 不應該取代 JavaScript,但它可以在你注意到 CPU 佔用的情況下對其進行補充。 對於大多數 Web 應用程序,JavaScript 更適合,而 WebAssembly 最適合用於計算密集型 Web 應用程序,例如游戲。
如果您想了解有關 WebAssembly 的更多信息:
- Lin Clark 為 WebAssembly 編寫了一個詳盡的系列文章,Milica Mihajlija 提供了有關如何在瀏覽器中運行本機代碼的總體概述,為什麼要這樣做,以及這對 JavaScript 和 Web 開發的未來意味著什麼。
- 我們如何使用 WebAssembly 將我們的 Web 應用程序加速 20 倍(案例研究)重點介紹瞭如何用編譯的 WebAssembly 取代緩慢的 JavaScript 計算並帶來顯著的性能改進的案例研究。
- Patrick Hamann 一直在談論 WebAssembly 日益重要的角色,他揭穿了一些關於 WebAssembly 的神話,探索了它的挑戰,我們可以在今天的應用程序中實際使用它。
- Google Codelabs 提供了 WebAssembly 簡介,這是一個 60 分鐘的課程,您將在其中學習如何使用 C 語言獲取本機代碼並將其編譯為 WebAssembly,然後直接從 JavaScript 調用它。
- Alex Danilo 在他的 Google I/O 演講中解釋了 WebAssembly 及其工作原理。 此外,Benedek Gagyi 分享了一個關於 WebAssembly 的實際案例研究,特別是團隊如何將其用作其 C++ 代碼庫到 iOS、Android 和網站的輸出格式。
仍然不確定何時使用 Web Workers、Web Assembly、流或 WebGL JavaScript API 來訪問 GPU? 加速 JavaScript 是一個簡短但有用的指南,它解釋了何時使用什麼以及為什麼使用 - 還帶有一個方便的流程圖和大量有用的資源。
- 我們是否只為舊版瀏覽器提供舊版代碼?
由於 ES2017 在現代瀏覽器中得到了很好的支持,我們可以使用babelEsmPlugin
僅轉換您所針對的現代瀏覽器不支持的 ES2017+ 功能。Houssein Djirdeh 和 Jason Miller 最近發布了一份關於如何轉譯和提供現代和遺留 JavaScript 的綜合指南,詳細介紹瞭如何使其與 Webpack 和 Rollup 一起使用,以及所需的工具。 您還可以估計可以在您的網站或應用程序包上減少多少 JavaScript。
所有主流瀏覽器都支持 JavaScript 模塊,因此使用 use
script type="module"
讓支持 ES 模塊的瀏覽器加載文件,而舊版瀏覽器可以加載帶有script nomodule
的舊版本。如今,我們可以編寫在瀏覽器中本地運行的基於模塊的 JavaScript,無需轉譯器或捆綁器。
<link rel="modulepreload">
標頭提供了一種啟動模塊腳本的早期(和高優先級)加載的方法。 基本上,這是一種幫助最大化帶寬使用的好方法,它告訴瀏覽器它需要獲取什麼,這樣它就不會在那些漫長的往返過程中遇到任何事情。 此外,Jake Archibald 發表了一篇詳細的文章,其中包含值得一讀的 ES 模塊的陷阱和注意事項。
- 通過增量解耦識別和重寫遺留代碼。
長期存在的項目往往會積聚灰塵和過時的代碼。 重新審視您的依賴關係並評估需要多少時間來重構或重寫最近造成問題的遺留代碼。 當然,這總是一項艱鉅的任務,但是一旦您了解遺留代碼的影響,您就可以從增量解耦開始。首先,設置指標來跟踪遺留代碼調用的比率是保持不變還是下降,而不是上升。 公開勸阻團隊不要使用該庫,並確保您的 CI 會在拉取請求中使用它時提醒開發人員。 polyfill 可以幫助從遺留代碼過渡到使用標準瀏覽器功能的重寫代碼庫。
- 識別並刪除未使用的 CSS/JS 。
Chrome 中的 CSS 和 JavaScript 代碼覆蓋率可讓您了解哪些代碼已執行/應用,哪些尚未執行/應用。 您可以開始記錄覆蓋率,在頁面上執行操作,然後探索代碼覆蓋率結果。 檢測到未使用的代碼後,使用import()
找到這些模塊並延遲加載(查看整個線程)。 然後重複覆蓋率配置文件並驗證它現在在初始加載時發送的代碼更少。您可以使用 Puppeteer 以編程方式收集代碼覆蓋率。 Chrome 也允許您導出代碼覆蓋率結果。 正如 Andy Davies 所指出的,您可能希望收集現代和舊版瀏覽器的代碼覆蓋率。
Puppetter 還有許多其他用例和工具可能需要更多介紹:
- Puppeteer 的用例,例如,自動視覺差異或在每次構建時監控未使用的 CSS,
- 使用 Puppeteer 的 Web 性能食譜,
- 用於記錄和生成 Pupeeteer 和 Playwright 腳本的有用工具,
- 此外,您甚至可以直接在 DevTools 中記錄測試,
- Nitay Neeman 對 Puppeteer 的全面概述,包括示例和用例。
此外,purgecss、UnCSS 和 Helium 可以幫助您從 CSS 中刪除未使用的樣式。 如果您不確定某處是否使用了可疑代碼,您可以遵循 Harry Roberts 的建議:為特定類創建一個 1×1px 透明 GIF 並將其放入
dead/
目錄,例如/assets/img/dead/comments.gif
。之後,您將特定圖像設置為 CSS 中相應選擇器的背景,然後等待幾個月,如果該文件將出現在您的日誌中。 如果沒有條目,則沒有人會在他們的屏幕上呈現該遺留組件:您可能可以繼續將其全部刪除。
對於I-feel-adventurous部門,您甚至可以通過使用 DevTools 監控 DevTools,通過一組頁面自動收集未使用的 CSS。
- 修剪 JavaScript 包的大小。
正如 Addy Osmani 所指出的,當您只需要一小部分時,您很有可能會發布完整的 JavaScript 庫,以及用於不需要它們的瀏覽器的過時 polyfill,或者只是重複代碼。 為避免開銷,請考慮使用 webpack-libs-optimizations 在構建過程中刪除未使用的方法和 polyfill。檢查並審查您發送到舊版瀏覽器和現代瀏覽器的polyfill ,並對它們更具戰略性。 看看 polyfill.io,它是一個服務,它接受對一組瀏覽器功能的請求,並僅返回請求瀏覽器所需的 polyfill。
將捆綁審計也添加到您的常規工作流程中。 您多年前添加的重型庫可能有一些輕量級替代品,例如 Moment.js(現已停產)可以替換為:
- 原生國際化 API,
- Day.js 具有熟悉的 Moment.js API 和模式,
- 日期-fns 或
- 盧克森。
- 您還可以使用 Skypack Discover,它將人工審核的包裹推薦與註重質量的搜索相結合。
Benedikt Rotsch 的研究表明,從 Moment.js 切換到 date-fns 可以為 3G 和低端手機上的 First Paint 節省大約 300 毫秒。
對於包審計,Bundlephobia 可以幫助您找到將 npm 包添加到包中的成本。 size-limit 通過 JavaScript 執行時間的詳細信息擴展了基本的包大小檢查。 您甚至可以將這些成本與 Lighthouse 自定義審計相結合。 這也適用於框架。 通過刪除或修剪 Vue MDC 適配器(Vue 的材料組件),樣式從 194KB 下降到 10KB。
還有許多其他工具可幫助您就依賴項的影響和可行的替代方案做出明智的決定:
- webpack-bundle-analyzer
- 源地圖瀏覽器
- 捆綁好友
- 捆綁恐懼症
- Webpack 分析顯示了為什麼將特定模塊包含在包中。
- bundle-wizard 還為整個頁面構建依賴關係圖。
- Webpack 大小插件
- 可視化代碼的導入成本
作為交付整個框架的替代方案,您可以修剪您的框架並將其編譯為不需要額外代碼的原始 JavaScript 包。 Svelte 做到了,Rawact Babel 插件也在構建時將 React.js 組件轉換為原生 DOM 操作。 為什麼? 好吧,正如維護者解釋的那樣,“react-dom 包含所有可以渲染的可能組件/HTMLElement 的代碼,包括用於增量渲染、調度、事件處理等的代碼。但是有些應用程序不需要所有這些功能(最初頁面加載)。對於此類應用程序,使用本機 DOM 操作來構建交互式用戶界面可能是有意義的。
- 我們是否使用部分補水?
隨著應用程序中使用的 JavaScript 數量,我們需要想辦法盡可能少地發送給客戶端。 這樣做的一種方法——我們已經簡要介紹過——是部分補水。 這個想法很簡單:不是執行 SSR 然後將整個應用程序發送到客戶端,而是只將應用程序的一小部分 JavaScript 發送到客戶端然後進行水合。 我們可以將其視為在一個靜態網站上具有多個渲染根的多個微型 React 應用程序。在文章“部分水合的案例(使用 Next 和 Preact)”中,Lukas Bombach 解釋了德國新聞媒體之一 Welt.de 背後的團隊如何通過部分水合取得更好的性能。 您還可以查看具有解釋和代碼片段的 next-super-performance GitHub 存儲庫。
您還可以考慮其他選擇:
- Preact 和 Eleventy 的部分水合作用,
- React GitHub repo 中的漸進式水合作用,
- Vue.js 中的惰性水合(GitHub 存儲庫),
- 當用戶與需要它的 UI 交互時,導入交互模式以延遲加載非關鍵資源(例如組件、嵌入)。
Jason Miller 發布了關於如何使用 React 實現漸進式水合作用的工作演示,因此您可以立即使用它們:演示 1、演示 2、演示 3(也可在 GitHub 上獲得)。 另外,您可以查看 react-prerendered-component 庫。
- 我們是否優化了 React/SPA 的策略?
在單頁應用程序中苦苦掙扎? Jeremy Wagner 探討了客戶端框架性能對各種設備的影響,重點介紹了我們在使用某個設備時可能需要注意的一些含義和指導方針。因此,這是 Jeremy 建議用於 React 框架的 SPA 策略(但對於其他框架,它不應該有顯著變化):
- 盡可能將有狀態組件重構為無狀態組件。
- 盡可能預渲染無狀態組件以最小化服務器響應時間。 僅在服務器上渲染。
- 對於具有簡單交互性的有狀態組件,請考慮對該組件進行預渲染或服務器渲染,並將其交互性替換為與框架無關的事件偵聽器。
- 如果您必須在客戶端上對有狀態的組件進行水合,請在可見性或交互上使用惰性水合。
- 對於延遲水合的組件,請在主線程空閒時間使用
requestIdleCallback
安排它們的水合。
您可能還想追求或回顧其他一些策略:
- React 應用程序中 CSS-in-JS 的性能注意事項
- 通過僅在必要時加載 polyfill、使用動態導入和延遲水合來減少 Next.js 包大小。
- JavaScript 的秘密:關於 React、性能優化和多線程的故事,一個由 7 部分組成的冗長系列,關於使用 React 改進用戶界面挑戰,
- 如何測量 React 性能以及如何分析 React 應用程序。
- 在 React 中構建移動優先的網絡動畫,Alex Holachek 的精彩演講,以及幻燈片和 GitHub 存儲庫(感謝您的提示,Addy! )。
- webpack-libs-optimizations 是一個很棒的 GitHub 存儲庫,其中包含許多有用的特定於 Webpack 的與性能相關的優化。 由伊万·阿庫洛夫維護。
- Notion 中的 React 性能改進,Ivan Akulov 編寫的關於如何提高 React 性能的指南,其中包含大量有用的指針,可以使應用程序的速度提高 30% 左右。
- React Refresh Webpack Plugin(實驗性)允許熱重新加載,保留組件狀態,並支持鉤子和函數組件。
- 注意零包大小的 React 服務器組件,這是一種新提議的組件,不會對包大小產生影響。 該項目目前正在開發中,但非常感謝來自社區的任何反饋(Sophie Alpert 的出色解釋)。
- 您是否對 JavaScript 塊使用預測預取?
我們可以使用啟發式方法來決定何時預加載 JavaScript 塊。 Guess.js 是一組工具和庫,它們使用 Google Analytics 數據來確定用戶最有可能從給定頁面訪問的下一個頁面。 根據從 Google Analytics 或其他來源收集的用戶導航模式,Guess.js 構建了一個機器學習模型來預測和預取每個後續頁面所需的 JavaScript。因此,每個交互元素都會收到參與度的概率分數,並且基於該分數,客戶端腳本決定提前預取資源。 您可以將該技術集成到您的 Next.js 應用程序、Angular 和 React,並且還有一個 Webpack 插件可以自動執行設置過程。
顯然,您可能會提示瀏覽器使用不需要的數據並預取不需要的頁面,因此在預取請求的數量上保持相當保守是個好主意。 一個很好的用例是預取結帳中所需的驗證腳本,或者在關鍵的號召性用語進入視口時進行推測性預取。
需要不那麼複雜的東西嗎? 當出站鏈接出現在視口中時,DNStradamus 會對其進行 DNS 預取。 Quicklink、InstantClick 和 Instant.page 是小型庫,它們在空閒時間自動預取視口中的鏈接,以嘗試使下一頁導航加載更快。 Quicklink 允許預取 React Router 路由和 Javascript; 再加上它考慮到數據,所以它不會在 2G 或
Data-Saver
開啟時預取。 如果模式設置為使用視口預取(這是默認設置),則 Instant.page 也是如此。如果您想詳細了解預測預取的科學,Divya Tagtachian 有一個關於預測預取的藝術的精彩演講,涵蓋了從頭到尾的所有選項。
- 利用目標 JavaScript 引擎的優化。
研究哪些 JavaScript 引擎在您的用戶群中占主導地位,然後探索針對它們進行優化的方法。 例如,在針對 Blink 瀏覽器、Node.js 運行時和 Electron 中使用的 V8 進行優化時,對單體腳本使用腳本流。腳本流允許在下載開始後在單獨的後台線程上解析
async
或defer scripts
,因此在某些情況下可將頁面加載時間提高 10%。 實際上,在<head>
中使用<script defer>
,這樣瀏覽器可以及早發現資源,然後在後台線程上解析它。警告: Opera Mini 不支持腳本延遲,因此如果您正在為印度或非洲開發,
defer
將被忽略,導致在腳本評估之前阻塞渲染(感謝 Jeremy!) 。您還可以掛鉤到 V8 的代碼緩存,通過將庫從使用它們的代碼中分離出來,或者反過來,將庫及其使用合併到一個腳本中,將小文件組合在一起並避免使用內聯腳本。 或者甚至可以使用 v8-compile-cache。
一般來說,當談到 JavaScript 時,還有一些值得牢記的做法:
- JavaScript 的清潔代碼概念,用於編寫可讀、可重用和可重構代碼的大量模式集合。
- 您可以使用 CompressionStream API 壓縮來自 JavaScript 的數據,例如在上傳數據之前壓縮到 gzip(Chrome 80+)。
- 分離的窗口內存洩漏和修復 Web 應用程序中的內存洩漏是有關如何查找和修復棘手的 JavaScript 內存洩漏的詳細指南。 另外,您可以使用 DevTools 控制台中的 queryObjects(SomeConstructor)(謝謝,Mathias! )。
- 重新導出對加載和運行時性能不利,避免它們有助於顯著減小包大小。
- 我們可以通過在
options
參數中設置一個標誌來提高被動事件偵聽器的滾動性能。 因此瀏覽器可以立即滾動頁面,而不是在偵聽器完成之後。 (通過凱斯巴斯克)。 - 如果您有任何
scroll
或touch*
偵聽器,請將passive: true
傳遞給 addEventListener。 這告訴瀏覽器你不打算在裡面調用event.preventDefault()
,所以它可以優化它處理這些事件的方式。 (通過伊万·阿庫洛夫) - 我們可以使用 isInputPending() 實現更好的 JavaScript 調度,這是一個新的 API,它試圖通過網絡上用戶輸入的中斷概念來彌合加載和響應之間的差距,並允許 JavaScript 能夠檢查輸入而不屈服於瀏覽器。
- 您還可以在事件偵聽器執行後自動刪除它。
- Firefox 最近發布的 Warp,是 SpiderMonkey(在 Firefox 83 中提供)、基線解釋器的重要更新,還有一些可用的 JIT 優化策略。
- 總是更喜歡自託管第三方資產。
同樣,默認情況下自託管您的靜態資產。 通常假設如果許多站點使用相同的公共 CDN 和相同版本的 JavaScript 庫或網絡字體,那麼訪問者將使用已經緩存在瀏覽器中的腳本和字體登陸我們的站點,從而大大加快他們的體驗. 但是,這不太可能發生。出於安全原因,為了避免指紋識別,瀏覽器一直在實施分區緩存,該緩存早在 2013 年在 Safari 中引入,去年在 Chrome 中引入。 因此,如果兩個站點指向完全相同的第三方資源 URL,則每個域都會下載一次代碼,並且由於隱私問題,緩存會被“沙盒化”到該域(感謝 David Calhoun! )。 因此,使用公共 CDN 不會自動帶來更好的性能。
此外,值得注意的是資源不會像我們預期的那樣長期存在於瀏覽器的緩存中,並且第一方資產比第三方資產更有可能留在緩存中。 因此,自託管通常更可靠、更安全,性能也更好。
- 限制第三方腳本的影響。
在所有性能優化到位後,我們通常無法控制來自業務需求的第三方腳本。 第三方腳本指標不受最終用戶體驗的影響,因此一個單獨的腳本經常會調用一長串令人討厭的第三方腳本,從而破壞了專門的性能工作。 為了控制和減輕這些腳本帶來的性能損失,僅僅通過資源提示(即dns-prefetch
或preconnect
)推遲它們的加載和執行以及預熱連接是不夠的。目前,所有 JavaScript 代碼執行時間的 57% 都花在了第三方代碼上。 移動站點訪問12 個第三方域的中位數,平均有 37 個不同的請求(或每個第三方大約 3 個請求)。
此外,這些第三方經常邀請第四方腳本加入,最終導致巨大的性能瓶頸,有時甚至會影響頁面上的第八方腳本。 因此,定期審核您的依賴項和標籤管理器可能會帶來代價高昂的意外。
另一個問題,正如 Yoav Weiss 在他關於第三方腳本的演講中所解釋的那樣,在許多情況下,這些腳本下載的資源是動態的。 資源在頁面加載之間會發生變化,因此我們不一定知道將從哪些主機下載資源以及它們將是什麼資源。
如上所示,延遲可能只是一個開始,因為第三方腳本也會從您的應用程序中竊取帶寬和 CPU 時間。 我們可以更積極一點,只在我們的應用程序初始化時才加載它們。
/* Before */ const App = () => { return <div> <script> window.dataLayer = window.dataLayer || []; function gtag(){...} gtg('js', new Date()); </script> </div> } /* After */ const App = () => { const[isRendered, setRendered] = useState(false); useEffect(() => setRendered(true)); return <div> {isRendered ? <script> window.dataLayer = window.dataLayer || []; function gtag(){...} gtg('js', new Date()); </script> : null} </div> }
在一篇關於“減少第三方標籤對網站速度的影響”的精彩帖子中,Andy Davies 探討了一種盡量減少第三方足蹟的策略——從確定他們的成本到減少他們的影響。
根據 Andy 的說法,標籤影響網站速度的方式有兩種——它們在訪問者的設備上爭奪網絡帶寬和處理時間,並且根據它們的實施方式,它們還可以延遲 HTML 解析。 因此,第一步是通過使用 WebPageTest 測試帶有和不帶有腳本的站點來確定第三方的影響。 借助 Simon Hearne 的請求圖,我們還可以在頁面上可視化第三方以及有關其大小、類型和触發負載的詳細信息。
最好自託管並使用單個主機名,但也使用請求映射來公開第四方調用並檢測腳本何時更改。 您可以使用 Harry Roberts 的方法來審核第三方並生成類似這樣的電子表格(也可以查看 Harry 的審核工作流程)。
之後,我們可以探索現有腳本的輕量級替代方案,並慢慢用更輕量級的選項替換重複項和主要罪魁禍首。 也許一些腳本可以用它們的後備跟踪像素代替完整的標籤。
如果它不可行,我們至少可以延遲加載具有外觀的第三方資源,即看起來類似於實際嵌入的第三方的靜態元素,但沒有功能,因此對頁面加載的負擔要少得多。 那麼,訣竅是僅在交互上加載實際嵌入。
例如,我們可以使用:
- lite-vimeo-embed 用於 Vimeo 播放器,
- 用於 Vimeo 播放器的 lite-vimeo,
- lite-youtube-embed 用於 YouTube 播放器,
- react-live-chat-loader 用於實時聊天(案例研究和另一個案例研究),
- iframe 的惰性框架。
標籤管理器通常規模很大的原因之一是因為同時運行的許多同時進行的實驗,以及許多用戶細分、頁面 URL、站點等,因此根據 Andy 的說法,減少它們可以減少兩者下載大小和在瀏覽器中執行腳本所需的時間。
然後是防閃爍片段。 Google Optimize、Visual Web Optimizer (VWO) 等第三方都一致使用它們。 這些片段通常與運行A/B 測試一起注入:為了避免在不同測試場景之間閃爍,它們使用
opacity: 0
隱藏文檔的body
,然後添加一個在幾秒鐘後調用的函數以恢復opacity
. 由於大量的客戶端執行成本,這通常會導致渲染的大量延遲。因此,跟踪觸發防閃爍超時的頻率並減少超時。 默認阻止頁面顯示最多 4 秒,這將破壞轉化率。 根據 Tim Kadlec 的說法,“朋友不要讓朋友做客戶端 A/B 測試”。 CDN 上的服務器端 A/B 測試(例如邊緣計算或邊緣切片重新渲染)始終是性能更高的選項。
如果您必須與全能的Google Tag Manager打交道,Barry Pollard 提供了一些指南來遏制 Google Tag Manager 的影響。 此外,Christian Schaefer 還探索了加載廣告的策略。
注意:一些第三方小部件隱藏在審計工具之外,因此它們可能更難以發現和測量。 要對第三方進行壓力測試,請在 DevTools 的性能配置文件頁面中檢查自下而上的摘要,測試如果請求被阻止或超時會發生什麼——對於後者,您可以使用 WebPageTest 的 Blackhole 服務器
blackhole.webpagetest.org
可以在您的hosts
文件中指向特定域。那我們有什麼選擇呢? 考慮使用服務工作者通過超時來加速資源下載,如果資源在特定超時內沒有響應,則返回一個空響應來告訴瀏覽器繼續解析頁面。 您還可以記錄或阻止不成功或不滿足特定條件的第三方請求。 如果可以,請從您自己的服務器而不是從供應商的服務器加載 3rd-party-script 並延遲加載它們。
另一種選擇是建立內容安全策略 (CSP)以限制第三方腳本的影響,例如禁止下載音頻或視頻。 最好的選擇是通過
<iframe>
嵌入腳本,以便腳本在 iframe 的上下文中運行,因此無法訪問頁面的 DOM,並且不能在您的域上運行任意代碼。 使用sandbox
屬性可以進一步限制 iframe,因此您可以禁用 iframe 可能執行的任何功能,例如阻止腳本運行、阻止警報、表單提交、插件、訪問頂部導航等等。您還可以通過使用功能策略的瀏覽器內性能檢查來檢查第三方,這是一項相對較新的功能,可讓您
選擇加入或退出您網站上的某些瀏覽器功能。 (作為旁注,它還可用於避免過大和未優化的圖像、未調整大小的媒體、同步腳本等)。 目前支持基於 Blink 的瀏覽器。 /* Via Tim Kadlec. https://timkadlec.com/remembers/2020-02-20-in-browser-performance-linting-with-feature-policies/ */ /* Block the use of the Geolocation API with a Feature-Policy header. */ Feature-Policy: geolocation 'none'
/* Via Tim Kadlec. https://timkadlec.com/remembers/2020-02-20-in-browser-performance-linting-with-feature-policies/ */ /* Block the use of the Geolocation API with a Feature-Policy header. */ Feature-Policy: geolocation 'none'
由於許多第三方腳本在 iframe 中運行,您可能需要徹底限制它們的允許範圍。 沙盒 iframe 總是一個好主意,每個限制都可以通過
sandbox
屬性上的一些allow
值來解除。 幾乎所有地方都支持沙盒,因此將第三方腳本限制在應允許的最低限度。考慮使用交叉口觀察器; 這將使廣告能夠被 iframe ,同時仍然調度事件或從 DOM 獲取他們需要的信息(例如廣告可見性)。 注意新策略,例如功能策略、資源大小限制和 CPU/帶寬優先級,以限制會降低瀏覽器速度的有害 Web 功能和腳本,例如同步腳本、同步 XHR 請求、 document.write和過時的實現。
最後,在選擇第三方服務時,請考慮查看 Patrick Hulce 的 ThirdPartyWeb.Today,該服務按類別(分析、社交、廣告、託管、標籤管理器等)對所有第三方腳本進行分組,並可視化實體腳本的時長執行(平均)。 顯然,最大的實體對其所在頁面的性能影響最差。 只需瀏覽頁面,您就會了解您應該期待的性能足跡。
啊,不要忘記通常的嫌疑人:我們可以使用靜態社交共享按鈕(例如 SSBG)和交互式地圖的靜態鏈接而不是交互式地圖,而不是使用第三方小部件進行共享。
- 正確設置 HTTP 緩存標頭。
緩存似乎是一件顯而易見的事情,但要做到正確可能相當困難。 我們需要仔細檢查expires
、max-age
、cache-control
和其他 HTTP 緩存標頭是否已正確設置。 如果沒有適當的 HTTP 緩存標頭,瀏覽器將自動將它們設置為自last-modified
以來經過時間的 10%,最終導致潛在的緩存不足和過度緩存。通常,資源應該可以在很短的時間內(如果它們可能會更改)或無限期(如果它們是靜態的)可以緩存——您可以在需要時在 URL 中更改它們的版本。 您可以將其稱為 Cache-Forever 策略,其中我們可以將
Cache-Control
和Expires
標頭中繼到瀏覽器,以僅允許資產在一年內過期。 因此,如果資產在緩存中,瀏覽器甚至不會發出請求。例外是 API 響應(例如
/api/user
)。 為了防止緩存,我們可以使用private, no store
,而不是max-age=0, no-store
:Cache-Control: private, no-store
使用
Cache-control: immutable
以避免在用戶點擊重新加載按鈕時重新驗證長顯式緩存生命週期。 對於重新加載的情況,immutable
的保存了 HTTP 請求並改善了動態 HTML 的加載時間,因為它們不再與大量 304 響應競爭。我們想要使用
immutable
的典型示例是名稱中帶有哈希的 CSS/JavaScript 資產。 對於他們,我們可能希望盡可能長時間地緩存,並確保他們永遠不會被重新驗證:Cache-Control: max-age: 31556952, immutable
根據 Colin Bendell 的研究,
immutable
將 304 重定向減少了大約 50%,即使使用了max-age
,客戶端仍然會在刷新時重新驗證並阻止。 Firefox、Edge 和 Safari 都支持它,而 Chrome 仍在爭論這個問題。據 Web Almanac 稱,“它的使用率已經增長到 3.5%,並且被廣泛用於 Facebook 和 Google 第三方響應中。”
你還記得舊時重新驗證的好方法嗎? 當我們用
Cache-Control
響應頭指定緩存時間時(例如Cache-Control: max-age=604800
),在max-age
過期後,瀏覽器會重新獲取請求的內容,導致頁面加載速度變慢。stale-while-revalidate
可以避免這種減速; 它基本上定義了一個額外的時間窗口,在此期間緩存可以使用陳舊的資產,只要它在後台重新驗證它是異步的。 因此,它“隱藏”了客戶端的延遲(在網絡和服務器上)。在 2019 年 6 月至 2019 年 7 月, Chrome 和 Firefox 推出了對 HTTP Cache-Control 標頭中的
stale-while-revalidate
的支持,因此它應該會改善後續頁面加載延遲,因為過時的資產不再處於關鍵路徑中。 結果:重複視圖的 RTT 為零。警惕可變標頭,尤其是與 CDN 相關的標頭,並註意 HTTP 表示變體,這有助於避免在新請求與先前請求略有不同(但不顯著)時進行額外的往返驗證(感謝 Guy 和 Mark ! )。
此外,請仔細檢查您沒有發送不必要的標頭(例如
x-powered-by
、pragma
、x-ua-compatible
、expires
、X-XSS-Protection
等),並且您包含有用的安全和性能標頭(例如作為Content-Security-Policy
、X-Content-Type-Options
等)。 最後,請記住單頁應用程序中 CORS 請求的性能成本。注意:我們經常假設緩存的資產會被立即檢索,但研究表明從緩存中檢索對象可能需要數百毫秒。 事實上,根據 Simon Hearne 的說法,“有時網絡可能比緩存更快,並且從緩存中檢索資產的成本可能很高,因為緩存的資產(不是文件大小)和用戶的設備數量很大。例如:Chrome OS 平均緩存檢索從具有 5 個緩存資源的 ~50ms 到具有 25 個資源的 ~100ms 翻倍”。
此外,我們通常假設捆綁包大小不是一個大問題,用戶會下載一次,然後使用緩存版本。 同時,通過 CI/CD,我們每天多次將代碼推送到生產環境,緩存每次都會失效,因此對緩存有策略性很重要。
談到緩存,有很多資源值得一讀:
- 平民緩存控制,與 Harry Roberts 一起深入研究緩存的所有內容。
- Heroku 的 HTTP 緩存標頭入門,
- Jake Archibald 的緩存最佳實踐,
- Ilya Grigorik 的 HTTP 緩存入門,
- 通過 Jeff Posnick 的 stale-while-revalidate 保持新鮮。
- CS 可視化:Lydia Hallie 的 CORS 是關於 CORS、它如何工作以及如何理解它的出色解釋者。
- 談到 CORS,這裡是 Eric Portis 對同源策略的一些複習。
交付優化
- 我們是否使用
defer
來異步加載關鍵的 JavaScript?
當用戶請求一個頁面時,瀏覽器獲取 HTML 並構造 DOM,然後獲取 CSS 並構造 CSSOM,然後通過匹配 DOM 和 CSSOM 生成渲染樹。 如果需要解析任何 JavaScript,瀏覽器在解析之前不會開始渲染頁面,從而延遲渲染。 作為開發人員,我們必須明確告訴瀏覽器不要等待並開始渲染頁面。 對腳本執行此操作的方法是使用 HTML 中的defer
和async
屬性。在實踐中,事實證明最好使用
defer
而不是async
。 啊,又有什麼區別呢? 根據 Steve Souders 的說法,一旦async
腳本到達,它們就會立即執行——只要腳本準備好。 如果這種情況發生得非常快,例如當腳本處於緩存中時,它實際上會阻塞 HTML 解析器。 使用defer
,瀏覽器在解析 HTML 之前不會執行腳本。 因此,除非您需要在開始渲染之前執行 JavaScript,否則最好使用defer
。 此外,多個異步文件將以不確定的順序執行。值得注意的是,關於
async
和defer
存在一些誤解。 最重要的是,async
並不意味著只要腳本準備好,代碼就會運行。 這意味著只要腳本準備好並且所有先前的同步工作完成,它將運行。 用 Harry Roberts 的話來說,“如果你在同步腳本之後放置一個async
腳本,那麼你的async
腳本只會和最慢的同步腳本一樣快。”此外,不建議同時使用
async
和defer
。 現代瀏覽器同時支持這兩種屬性,但只要同時使用這兩種屬性,async
總是會勝出的。如果您想深入了解更多細節,Milica Mihajlija 編寫了一份關於更快構建 DOM 的非常詳細的指南,詳細介紹了推測解析、異步和延遲。
- 使用 IntersectionObserver 和優先級提示延遲加載昂貴的組件。
一般來說,建議延遲加載所有昂貴的組件,例如繁重的 JavaScript、視頻、iframe、小部件和潛在的圖像。 本機延遲加載已經可用於具有loading
屬性的圖像和 iframe(僅限 Chromium)。 在幕後,此屬性延遲資源的加載,直到它到達與視口的計算距離。<!-- Lazy loading for images, iframes, scripts. Probably for images outside of the viewport. --> <img loading="lazy" ... /> <iframe loading="lazy" ... /> <!-- Prompt an early download of an asset. For critical images, eg hero images. --> <img loading="eager" ... /> <iframe loading="eager" ... />
該閾值取決於幾件事,從要獲取的圖像資源的類型到有效的連接類型。 但在 Android 上使用 Chrome 進行的實驗表明,在 4G 上,97.5% 的延遲加載的首屏圖像在可見後 10 毫秒內完全加載,因此應該是安全的。
我們還可以在
<script>
、<img>
或<link>
元素(僅閃爍)上使用importance
屬性(high
或low
)。 事實上,這是一種在輪播中取消圖像優先級以及重新確定腳本優先級的好方法。 但是,有時我們可能需要更精細的控制。<!-- When the browser assigns "High" priority to an image, but we don't actually want that. --> <img src="less-important-image.svg" importance="low" ... /> <!-- We want to initiate an early fetch for a resource, but also deprioritize it. --> <link rel="preload" importance="low" href="/script.js" as="script" />
執行稍微複雜一點的延遲加載的最高效方法是使用 Intersection Observer API,它提供了一種異步觀察目標元素與祖先元素或頂級文檔視口的交集變化的方法。 基本上,您需要創建一個新的
IntersectionObserver
對象,該對象接收一個回調函數和一組選項。 然後我們添加一個目標來觀察。回調函數在目標變得可見或不可見時執行,因此當它攔截視口時,您可以在元素變得可見之前開始執行一些操作。 事實上,我們可以通過
rootMargin
(根周圍的邊距)和threshold
(單個數字或數字數組,指示我們瞄準的目標可見性的百分比)對何時調用觀察者的回調進行精細控制。Alejandro Garcia Anglada 發布了一個關於如何實際實現它的方便教程,Rahul Nanwani 寫了一篇關於延遲加載前景和背景圖像的詳細文章,Google Fundamentals 也提供了關於延遲加載圖像和視頻的詳細教程,以及 Intersection Observer。
還記得帶有移動和粘性物體的藝術指導的長篇故事嗎? 您也可以使用 Intersection Observer 實現高性能的滾動顯示。
再次檢查您可以延遲加載的其他內容。 甚至延遲加載翻譯字符串和表情符號也會有所幫助。 通過這樣做,Mobile Twitter 成功地將新的國際化管道的 JavaScript 執行速度提高了 80%。
一個簡短的警告:值得注意的是延遲加載應該是一個例外而不是規則。 延遲加載您真正希望人們快速看到的任何內容可能是不合理的,例如產品頁面圖像、英雄圖像或主導航變為交互所需的腳本。
- 逐步加載圖像。
您甚至可以通過向頁面添加漸進式圖像加載來將延遲加載提升到一個新的水平。 與 Facebook、Pinterest、Medium 和 Wolt 類似,您可以先加載低質量甚至模糊的圖像,然後隨著頁面繼續加載,使用 BlurHash 技術或 LQIP(低質量圖像佔位符)將它們替換為完整質量版本技術。如果這些技術是否改善了用戶體驗,意見會有所不同,但它肯定會縮短首次內容繪製的時間。 我們甚至可以通過使用 SQIP 來自動化它,該 SQIP 創建一個低質量版本的圖像作為 SVG 佔位符,或帶有 CSS 線性漸變的漸變圖像佔位符。
這些佔位符可以嵌入到 HTML 中,因為它們自然可以很好地使用文本壓縮方法進行壓縮。 在他的文章中,Dean Hume 描述瞭如何使用 Intersection Observer 來實現這種技術。
倒退? 如果瀏覽器不支持交叉點觀察器,我們仍然可以延遲加載 polyfill 或立即加載圖像。 甚至還有一個圖書館。
想要更高級? 您可以跟踪圖像並使用原始形狀和邊緣來創建輕量級 SVG 佔位符,首先加載它,然後從占位符矢量圖像過渡到(加載的)位圖圖像。
- 您是否延遲渲染
content-visibility
?
對於包含大量內容塊、圖像和視頻的複雜佈局,解碼數據和渲染像素可能是一項相當昂貴的操作——尤其是在低端設備上。 使用content-visibility: auto
,我們可以在容器位於視口之外時提示瀏覽器跳過子項的佈局。例如,您可能會在初始加載時跳過頁腳和後期部分的呈現:
footer { content-visibility: auto; contain-intrinsic-size: 1000px; /* 1000px is an estimated height for sections that are not rendered yet. */ }
注意content-visibility: auto; 表現得像溢出:隱藏; ,但您可以通過應用
padding-left
和padding-right
而不是默認的margin-left: auto;
,margin-right: auto;
和聲明的寬度。 填充基本上允許元素溢出內容框並進入填充框,而不會離開整個盒子模型並被切斷。另外,請記住,當最終呈現新內容時,您可能會引入一些 CLS,因此最好將
contain-intrinsic-size
與適當大小的佔位符一起使用(謝謝,Una! )。Thijs Terluin 有更多關於這兩個屬性以及瀏覽器如何計算 contains
contain-intrinsic-size
的詳細信息,Malte Ubl 展示瞭如何計算它,Jake 和 Surma 的簡短視頻解釋器解釋了它是如何工作的。如果您需要更細化,使用 CSS 包含,如果您只需要其他元素的大小、對齊或計算樣式,您可以手動跳過 DOM 節點後代的佈局、樣式和繪製工作——或者該元素當前是畫布外。
- 您是否使用
decoding="async"
延遲解碼?
有時內容會出現在屏幕外,但我們希望確保它在客戶需要時可用——理想情況下,不阻塞關鍵路徑中的任何內容,而是異步解碼和呈現。 我們可以使用decodedecoding="async"
來授予瀏覽器從主線程解碼圖像的權限,避免用戶對用於解碼圖像的CPU時間的影響(通過Malte Ubl):<img decoding="async" … />
或者,對於屏幕外的圖像,我們可以先顯示一個佔位符,當圖像在視口內時,使用 IntersectionObserver 觸發網絡調用以在後台下載圖像。 此外,如果 Image Decode API 不可用,我們可以將渲染推遲到使用 img.decode() 解碼或下載圖像。
例如,在渲染圖像時,我們可以使用淡入動畫。 Katie Hempenius 和 Addy Osmani 在他們的演講 Speed at Scale: Web Performance Tips and Tricks from the Trenches 中分享了更多見解。
- 您是否生成並提供關鍵的 CSS?
為了確保瀏覽器盡快開始呈現您的頁面,收集開始呈現頁面第一個可見部分所需的所有 CSS(稱為“關鍵 CSS”或“首屏 CSS”已成為一種常見做法") 並將其包含在頁面的<head>
中,從而減少往返。 由於在慢啟動階段交換的包的大小有限,關鍵 CSS 的預算約為 14KB。如果超出此範圍,瀏覽器將需要額外的往返來獲取更多樣式。 CriticalCSS 和 Critical 使您能夠為您正在使用的每個模板輸出關鍵 CSS。 不過,根據我們的經驗,沒有任何自動系統比為每個模板手動收集關鍵 CSS 更好,而這確實是我們最近重新採用的方法。
然後,您可以使用 critters Webpack 插件內聯關鍵 CSS 並延遲加載其餘部分。 如果可能,請考慮使用 Filament Group 使用的條件內聯方法,或動態將內聯代碼轉換為靜態資源。
如果您當前使用諸如 loadCSS 之類的庫異步加載完整的 CSS ,則實際上沒有必要。 使用
media="print"
,您可以欺騙瀏覽器異步獲取 CSS,但在加載後應用到屏幕環境。 (謝謝,斯科特! )<!-- Via Scott Jehl. https://www.filamentgroup.com/lab/load-css-simpler/ --> <!-- Load CSS asynchronously, with low priority --> <link rel="stylesheet" href="full.css" media="print" onload="this.media='all'" />
在收集每個模板的所有關鍵 CSS 時,通常只探索“首屏”區域。 但是,對於復雜的佈局,最好將佈局的基礎也包括在內,以避免大量的重新計算和重新繪製成本,從而損害您的 Core Web Vitals 分數。
如果用戶得到一個直接鏈接到頁面中間的 URL 但 CSS 尚未下載怎麼辦? 在這種情況下,隱藏非關鍵內容變得很常見,例如
opacity: 0;
內聯 CSS 和opacity: 1
在完整的 CSS 文件中,並在 CSS 可用時顯示。 但它有一個主要缺點,因為連接速度較慢的用戶可能永遠無法閱讀頁面的內容。 這就是為什麼最好始終保持內容可見,即使它的樣式可能不正確。由於緩存,將關鍵 CSS(和其他重要資產)放在根域上的單獨文件中具有好處,有時甚至超過內聯。 Chrome 在請求頁面時會推測性地打開到根域的第二個 HTTP 連接,這樣就不需要 TCP 連接來獲取此 CSS。 這意味著您可以創建一組關鍵的-CSS 文件(例如, critical-homepage.css 、 critical-product-page.css等)並從您的根目錄提供它們,而無需內聯它們。 (謝謝,菲利普! )
需要注意的是:使用 HTTP/2,關鍵 CSS 可以存儲在單獨的 CSS 文件中,並通過服務器推送交付,而不會使 HTML 膨脹。 問題是服務器推送對於跨瀏覽器的許多陷阱和競爭條件很麻煩。 它從來沒有得到一致的支持,並且存在一些緩存問題(參見 Hooman Beheshti 演示文稿的幻燈片 114)。
事實上,這種影響可能是負面的,並且會使網絡緩衝區膨脹,從而阻止傳遞文檔中的真實幀。 因此,Chrome 計劃暫時取消對 Server Push 的支持也就不足為奇了。
- 嘗試重新組合 CSS 規則。
我們已經習慣了關鍵的 CSS,但還有一些優化可以超越這一點。 哈里·羅伯茨(Harry Roberts)進行了一項非凡的研究,結果令人驚訝。 例如,將主 CSS 文件拆分為單獨的媒體查詢可能是個好主意。 這樣,瀏覽器將檢索具有高優先級的關鍵 CSS,並以低優先級檢索其他所有內容——完全脫離關鍵路徑。另外,避免將
<link rel="stylesheet" />
放在async
片段之前。 如果腳本不依賴於樣式表,請考慮將阻塞腳本置於阻塞樣式之上。 如果是這樣,則將該 JavaScript 分成兩部分並將其加載到 CSS 的任一側。Scott Jehl 通過使用 service worker 緩存內聯 CSS 文件解決了另一個有趣的問題,如果您使用關鍵 CSS,這是一個常見的問題。 基本上,我們在
style
元素上添加一個 ID 屬性,以便使用 JavaScript 輕鬆找到它,然後一小段 JavaScript 找到該 CSS 並使用 Cache API 將其存儲在本地瀏覽器緩存中(內容類型為text/css
) 用於後續頁面。 為了避免在後續頁面上內聯,而是在外部引用緩存的資產,我們在第一次訪問網站時設置了一個 cookie。 瞧!值得注意的是,動態樣式也可能很昂貴,但通常僅在您依賴數百個同時呈現的組合組件的情況下。 因此,如果您使用 CSS-in-JS,請確保您的 CSS-in-JS 庫在您的 CSS 不依賴主題或道具時優化執行,並且不要過度組合樣式化的組件。 Aggelos Arvanitakis 分享了關於 CSS-in-JS 的性能成本的更多見解。
- 您是否流式傳輸響應?
流經常被遺忘和忽視,流提供了一個用於讀取或寫入異步數據塊的接口,在任何給定時間,內存中可能只有其中的一個子集可用。 基本上,它們允許發出原始請求的頁面在第一塊數據可用時立即開始處理響應,並使用針對流式傳輸優化的解析器來逐步顯示內容。我們可以從多個來源創建一個流。 例如,您可以讓 service worker構建一個流,其中 shell 來自緩存,而主體來自網絡,而不是提供一個空的 UI shell 並讓 JavaScript 填充它。 正如 Jeff Posnick 所指出的,如果您的 Web 應用程序由 CMS 提供支持,該 CMS 通過將部分模板拼接在一起來呈現 HTML,該模型將直接轉換為使用流式響應,模板邏輯複製在服務工作者而不是您的服務器中。 Jake Archibald 的 The Year of Web Streams 文章重點介紹瞭如何構建它。 性能提升非常明顯。
流式傳輸整個 HTML 響應的一個重要優勢是,在初始導航請求期間呈現的 HTML 可以充分利用瀏覽器的流式 HTML 解析器。 在頁面加載後插入到文檔中的 HTML 塊(這在通過 JavaScript 填充的內容很常見)不能利用這種優化。
瀏覽器支持? Chrome、Firefox、Safari 和 Edge 的部分支持仍然支持 API 和所有現代瀏覽器都支持的 Service Worker。 如果您再次感到冒險,您可以檢查流式請求的實驗性實現,它允許您在仍然生成正文的同時開始發送請求。 在 Chrome 85 中可用。
- 考慮使您的組件具有連接意識。
數據可能很昂貴,並且隨著負載的增加,我們需要尊重在訪問我們的網站或應用程序時選擇節省數據的用戶。 Save-Data 客戶端提示請求標頭允許我們為成本和性能受限的用戶定制應用程序和有效負載。實際上,您可以將對高 DPI 圖像的請求重寫為低 DPI 圖像、刪除 Web 字體、花哨的視差效果、預覽縮略圖和無限滾動、關閉視頻自動播放、服務器推送、減少顯示項目的數量和降低圖像質量,或者甚至改變你提供標記的方式。 Tim Vereecke 發表了一篇關於 data-s(h)aver 策略的非常詳細的文章,其中包含許多數據保存選項。
誰在使用
save-data
,您可能想知道? 全球 18% 的 Android Chrome 用戶啟用了精簡模式(啟用Save-Data
),而且這個數字可能會更高。 根據 Simon Hearne 的研究,廉價設備的選擇加入率最高,但也有很多異常值。 例如:加拿大用戶的選擇加入率超過 34%(相比之下,美國約為 7%),三星最新旗艦產品的全球用戶選擇加入率接近 18%。啟用
Save-Data
模式後,Chrome Mobile 將提供優化的體驗,即具有延遲腳本、強製font-display: swap
和強制延遲加載的代理網絡體驗。 自己構建體驗比依賴瀏覽器進行這些優化更明智。當前僅在 Chromium、Android 版本的 Chrome 或桌面設備上的數據保護程序擴展中支持該標頭。 最後,您還可以使用網絡信息 API 來根據網絡類型交付昂貴的 JavaScript 模塊、高分辨率圖像和視頻。 網絡信息 API,特別是
navigator.connection.effectiveType
使用RTT
、downlink
、effectiveType
值(以及其他一些值)來提供用戶可以處理的連接和數據的表示。在這種情況下,Max Bock 談到了連接感知組件,而 Addy Osmani 談到了自適應模塊服務。 例如,使用 React,我們可以編寫一個針對不同連接類型呈現不同的組件。 正如 Max 所建議的,新聞文章中的
<Media />
組件可能會輸出:-
Offline
:帶有alt
文字的佔位符, -
2G
/save-data
模式:低分辨率圖像, - 非 Retina 屏幕上的
3G
:中等分辨率圖像, - Retina 屏幕上的
3G
:高分辨率 Retina 圖像, -
4G
:高清視頻。
Dean Hume 使用服務工作者提供了類似邏輯的實際實現。 對於視頻,我們可以默認顯示視頻海報,然後在更好的連接上顯示“播放”圖標以及視頻播放器外殼、視頻元數據等。 作為不支持瀏覽器的後備方案,我們可以監聽
canplaythrough
事件並使用Promise.race()
如果canplaythrough
事件在 2 秒內未觸發,則使源加載超時。如果您想更深入地研究,這裡有一些資源可以開始:
- Addy Osmani 展示瞭如何在 React 中實現自適應服務。
- React Adaptive Loading Hooks & Utilities 提供了 React 的代碼片段,
- Netanel Basel 探索 Angular 中的連接感知組件,
- Theodore Vorilas 分享了在 Vue 中使用網絡信息 API 服務自適應組件的工作原理。
- Umar Hansa 展示瞭如何選擇性地下載/執行昂貴的 JavaScript。
-
- 考慮讓您的組件具有設備內存感知能力。
但是,網絡連接只為我們提供了一種關於用戶上下文的視角。 更進一步,您還可以使用設備內存 API 根據可用設備內存動態調整資源。navigator.deviceMemory
返回設備有多少 RAM(以 GB 為單位),向下舍入到最接近的 2 次冪。 該 API 還具有客戶端提示標頭Device-Memory
,它報告相同的值。獎勵: Umar Hansa 展示瞭如何通過動態導入推遲昂貴的腳本,以根據設備內存、網絡連接和硬件並發性來改變體驗。
- 預熱連接以加快交付速度。
使用資源提示來節省dns-prefetch
(在後台執行 DNS 查找)、preconnect
(要求瀏覽器在後台啟動連接握手(DNS、TCP、TLS))、prefetch
(要求瀏覽器請求資源)和preload
(預取資源而不執行資源等)。 在現代瀏覽器中得到很好的支持,很快就會支持 Firefox。還記得
prerender
嗎? 用於提示瀏覽器在後台構建整個頁面以進行下一次導航的資源提示。 實施問題非常成問題,從巨大的內存佔用和帶寬使用到多次註冊的分析點擊和廣告印象。不出所料,它已被棄用,但 Chrome 團隊已將其作為 NoState Prefetch 機制恢復。 事實上,Chrome 將
prerender
提示視為 NoState Prefetch,因此我們今天仍然可以使用它。 正如 Katie Hempenius 在那篇文章中解釋的那樣,“與預渲染一樣, NoState Prefetch 會提前獲取資源;但與預渲染不同的是,它不執行 JavaScript或提前渲染頁面的任何部分。”NoState Prefetch 僅使用約 45MiB 的內存,並且提取的子資源將以
IDLE
Net Priority 進行提取。 從 Chrome 69 開始,NoState Prefetch 添加了 header目的:Prefetch到所有請求,以使它們與正常瀏覽區分開來。此外,請注意預渲染替代方案和門戶,這是對隱私意識預渲染的一項新努力,它將為無縫導航提供內容的嵌入
preview
。使用資源提示可能是提高性能的最簡單方法,而且確實效果很好。 什麼時候用什麼? 正如 Addy Osmani 所解釋的,預加載我們知道很可能在當前頁面上使用的資源以及未來跨多個導航邊界的導航是合理的,例如用戶尚未訪問的頁面所需的 Webpack 包。
Addy 關於“在 Chrome 中加載優先級”的文章展示了 Chrome 如何準確地解釋資源提示,因此一旦您確定了哪些資產對渲染至關重要,您就可以為它們分配高優先級。 要查看您的請求的優先級,您可以在 Chrome DevTools 網絡請求表(以及 Safari)中啟用“優先級”列。
這些天的大部分時間,我們至少會使用
preconnect
和dns-prefetch
,並且我們會謹慎使用prefetch
、preload
和prerender
。 請注意,即使使用preconnect
和dns-prefetch
,瀏覽器也會限制並行查找/連接的主機數量,因此可以安全地根據優先級對它們進行排序(感謝 Philip Tellis! )。由於字體通常是頁面上的重要資產,有時請求瀏覽器下載帶有
preload
的關鍵字體是個好主意。 但是,請仔細檢查它是否真的有助於性能,因為在預加載字體時存在優先級難題:由於preload
被視為非常重要,它可以超越更關鍵的資源,例如關鍵的 CSS。 (謝謝,巴里! )<!-- Loading two rendering-critical fonts, but not all their weights. --> <!-- crossorigin="anonymous" is required due to CORS. Without it, preloaded fonts will be ignored. https://github.com/w3c/preload/issues/32 via https://twitter.com/iamakulov/status/1275790151642423303 --> <link rel="preload" as="font" href="Elena-Regular.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" /> <link rel="preload" as="font" href="Mija-Bold.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" />
<!-- Loading two rendering-critical fonts, but not all their weights. --> <!-- crossorigin="anonymous" is required due to CORS. Without it, preloaded fonts will be ignored. https://github.com/w3c/preload/issues/32 via https://twitter.com/iamakulov/status/1275790151642423303 --> <link rel="preload" as="font" href="Elena-Regular.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" /> <link rel="preload" as="font" href="Mija-Bold.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" />
由於
<link rel="preload">
接受media
屬性,您可以選擇根據@media
查詢規則有選擇地下載資源,如上所示。此外,我們可以使用
imagesrcset
和imagesizes
屬性更快地預加載後期發現的英雄圖像,或者通過 JavaScript 加載的任何圖像,例如電影海報:<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="image" href="poster.jpg" image image>
<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="image" href="poster.jpg" image image>
我們還可以將 JSON 預加載為fetch ,以便在 JavaScript 請求它之前發現它:
<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="fetch" href="foo.com/api/movies.json" crossorigin>
我們還可以動態加載 JavaScript,有效地延遲執行腳本。
/* Adding a preload hint to the head */ var preload = document.createElement("link"); link.href = "myscript.js"; link.rel = "preload"; link.as = "script"; document.head.appendChild(link); /* Injecting a script when we want it to execute */ var script = document.createElement("script"); script.src = "myscript.js"; document.body.appendChild(script);
需要記住的一些問題:
preload
有助於將資產的開始下載時間移到更接近初始請求的位置,但預加載的資產會落在與發出請求的頁面相關的內存緩存中。preload
與 HTTP 緩存配合得很好:如果項目已經在 HTTP 緩存中,則永遠不會發送網絡請求。因此,它對於後期發現的資源、通過
background-image
加載的英雄圖像、內聯關鍵 CSS(或 JavaScript)以及預加載其餘的 CSS(或 JavaScript)很有用。只有在瀏覽器從服務器接收到 HTML 並且前瞻解析器找到了
preload
標籤後,preload
標籤才能啟動預加載。 通過 HTTP 標頭預加載可能會更快一些,因為我們不需要等待瀏覽器解析 HTML 來啟動請求(儘管有爭議)。Early Hints 將進一步提供幫助,甚至可以在發送 HTML 的響應標頭之前啟動預加載(在 Chromium、Firefox 的路線圖上)。 另外,優先級提示將幫助我們指示腳本的加載優先級。
注意:如果您使用
preload
as
必須定義或不加載任何內容,加上沒有crossorigin
屬性的預加載字體將雙重獲取。 如果您使用的是prefetch
,請注意 Firefox 中的Age
標頭問題。
- 使用服務工作者進行緩存和網絡回退。
網絡上的任何性能優化都不會比用戶機器上本地存儲的緩存更快(但也有例外)。 如果您的網站通過 HTTPS 運行,我們可以將靜態資產緩存在 service worker 緩存中並存儲離線回退(甚至離線頁面)並從用戶的機器上檢索它們,而不是通過網絡。正如 Phil Walton 所建議的,對於服務工作者,我們可以通過以編程方式生成響應來發送更小的 HTML 有效負載。 Service Worker 可以從服務器請求其所需的最少數據(例如 HTML 內容部分、Markdown 文件、JSON 數據等),然後它可以以編程方式將該數據轉換為完整的 HTML 文檔。 因此,一旦用戶訪問站點並安裝了 service worker,用戶將永遠不會再次請求完整的 HTML 頁面。 性能影響可能相當可觀。
瀏覽器支持? 服務工作者受到廣泛支持,無論如何,後備是網絡。 它有助於提高性能嗎? 哦,是的,確實如此。 而且它正在變得越來越好,例如 Background Fetch 也允許通過 service worker 進行後台上傳/下載。
服務工作者有許多用例。 例如,您可以實現“離線保存”功能、處理損壞的圖像、在選項卡之間引入消息傳遞或根據請求類型提供不同的緩存策略。 一般來說,一個常見的可靠策略是將應用程序外殼與一些關鍵頁面一起存儲在服務工作者的緩存中,例如離線頁面、首頁以及對您的情況可能很重要的任何其他內容。
不過,有一些問題需要牢記。 有了服務工作者,我們需要注意 Safari 中的範圍請求(如果您將 Workbox 用於服務工作者,它有一個範圍請求模塊)。 如果你曾經偶然發現
DOMException: Quota exceeded.
瀏覽器控制台出錯,然後查看 Gerardo 的文章When 7KB equals 7MB。正如 Gerardo 所寫,“如果您正在構建一個漸進式 Web 應用程序,並且當您的 Service Worker 緩存從 CDN 提供的靜態資產時,您遇到了膨脹的緩存存儲,請確保跨域資源存在正確的 CORS 響應標頭,您不要緩存不透明的響應與您的服務人員無意中,您通過將
crossorigin
屬性添加到<img>
標記來選擇跨域圖像資產進入 CORS 模式。”有很多很棒的資源可以幫助您開始使用服務人員:
- Service Worker Mindset,它可以幫助您了解 Service Worker 如何在幕後工作以及在構建服務人員時要了解的內容。
- Chris Ferdinandi 提供了一系列關於服務工作者的精彩文章,解釋瞭如何創建離線應用程序並涵蓋了各種場景,從離線保存最近查看的頁面到為服務工作者緩存中的項目設置過期日期。
- Service Worker 陷阱和最佳實踐,包括一些關於範圍、延遲註冊 Service Worker 和 Service Worker 緩存的提示。
- Ire Aderinokun 與 Service Worker 的“離線優先”系列,其中包含預緩存應用程序外殼的策略。
- Service Worker:介紹如何使用 Service Worker 獲得豐富的離線體驗、定期後台同步和推送通知的實用技巧。
- 總是值得參考優秀的 ol' Jake Archibald 的離線食譜,其中包含許多關於如何烘焙自己的服務人員的食譜。
- Workbox 是一組專門為構建漸進式 Web 應用程序而構建的服務工作者庫。
- 您是否在 CDN/Edge 上運行服務器工作者,例如用於 A/B 測試?
在這一點上,我們已經習慣了在客戶端運行服務工作者,但是通過在服務器上實現它們的 CDN,我們也可以使用它們來調整邊緣的性能。例如,在 A/B 測試中,當 HTML 需要為不同的用戶改變其內容時,我們可以使用 CDN 服務器上的 Service Worker 來處理邏輯。 我們還可以流式傳輸 HTML 重寫以加速使用 Google 字體的網站。
- 優化渲染性能。
每當應用程序運行緩慢時,都會立即引起注意。 因此,我們需要確保在滾動頁面或動畫元素時沒有延遲,並且您始終保持每秒 60 幀的速度。 如果這不可能,那麼至少使每秒幀數保持一致比 60 到 15 的混合範圍更可取。使用 CSS 的will-change
通知瀏覽器哪些元素和屬性將發生變化。每當您遇到時,請在 DevTools 中調試不必要的重繪:
- 測量運行時渲染性能。 查看一些有關如何理解它的有用提示。
- 要開始使用,請查看 Paul Lewis 關於瀏覽器渲染優化的免費 Udacity 課程和 Georgy Marchuk 關於瀏覽器繪畫和 Web 性能考慮的文章。
- 在 Firefox DevTools 的“更多工具 → 渲染 → 畫圖閃爍”中啟用畫圖閃爍。
- 在 React DevTools 中,選中“突出顯示更新”並啟用“記錄每個組件呈現的原因”,
- 您還可以使用Why Did You Render,因此當重新渲染組件時,Flash 會通知您更改。
您使用的是砌體佈局嗎? 請記住,很快就可以單獨使用 CSS 網格構建 Masonry 佈局。
如果您想深入探討該主題,Nolan Lawson 在他的文章中分享了準確測量佈局性能的技巧,Jason Miller 也提出了替代技術。 我們還有一篇 Sergey Chikuyonok 撰寫的關於如何正確設置 GPU 動畫的文章。
注意:對 GPU 合成層的更改是最便宜的,因此如果您可以通過
opacity
和transform
僅觸發合成來擺脫困境,那麼您將走在正確的軌道上。 Anna Migas 在她關於調試 UI 渲染性能的演講中也提供了很多實用的建議。 要了解如何在 DevTools 中調試繪製性能,請查看 Umar 的繪製性能審核視頻。 - 您是否針對感知性能進行了優化?
雖然組件出現在頁面上的順序,以及我們如何為瀏覽器提供資源的策略很重要,但我們也不應該低估感知性能的作用。 這個概念涉及等待的心理方面,基本上是讓客戶在其他事情發生時保持忙碌或參與。 這就是感知管理、搶先開始、提前完成和容忍管理髮揮作用的地方。這是什麼意思呢? 在加載資產時,我們可以嘗試始終領先於客戶一步,因此在後台發生很多事情的同時,體驗感覺很快。 為了保持客戶的參與度,我們可以測試骨架屏幕(實現演示)而不是加載指示器,添加過渡/動畫,並且基本上在沒有什麼可以優化的情況下欺騙用戶體驗。
在他們關於 UI 骨架藝術的案例研究中,Kumar McMillan 分享了一些關於如何模擬動態列表、文本和最終屏幕的想法和技術,以及如何使用 React 考慮骨架思維。
但請注意:在部署之前應該測試骨架屏幕,因為一些測試表明骨架屏幕在所有指標上都表現最差。
- 您是否防止佈局變化和重繪?
在感知性能領域,可能更具破壞性的體驗之一是佈局轉換或回流,這是由重新縮放的圖像和視頻、網絡字體、注入的廣告或用實際內容填充組件的後期發現的腳本引起的。 結果,客戶可能開始閱讀一篇文章,只是被閱讀區域上方的佈局跳轉打斷。 這種體驗通常是突然的並且非常令人迷惑:這可能是加載需要重新考慮的優先級的情況。社區已經開發了一些技術和解決方法來避免回流。 一般來說,避免在現有內容之上插入新內容是個好主意,除非它是為了響應用戶交互而發生的。 始終在圖像上設置寬度和高度屬性,因此現代瀏覽器默認分配框並保留空間(Firefox、Chrome)。
對於圖像或視頻,我們可以使用 SVG 佔位符來保留媒體將出現的顯示框。這意味著當您需要保持其縱橫比時,該區域將被正確保留。 我們還可以為廣告和動態內容使用佔位符或後備圖像,以及預分配佈局插槽。
僅在不支持本地延遲加載時才加載外部腳本時,考慮使用本地延遲加載或混合延遲加載,而不是使用外部腳本延遲加載圖像。
如上所述,始終將 Web 字體重繪和從所有後備字體轉換為所有Web 字體一次 - 只需確保該切換不會太突然,通過使用 font-style-matcher 調整字體之間的行高和間距.
要覆蓋備用字體的字體指標以模擬 Web 字體,我們可以使用 @font-face 描述符來覆蓋字體指標(演示,在 Chrome 87 中啟用)。 (請注意,調整會因複雜的字體堆棧而變得複雜。)
對於後期 CSS,我們可以確保佈局關鍵的 CSS 內聯在每個模板的標題中。 更進一步:對於長頁面,當添加垂直滾動條時,它會將主要內容向左移動 16px。 為了儘早顯示滾動條,我們可以在
html
上添加overflow-y: scroll
以在第一次繪製時強制滾動條。 後者有幫助,因為當寬度改變時,由於折疊內容重排,滾動條會導致非平凡的佈局變化。 不過,應該主要發生在具有非覆蓋滾動條的平台上,例如 Windows。 但是:中斷position: sticky
,因為這些元素永遠不會滾動出容器。如果您處理在滾動時固定或粘在頁面頂部的頁眉,請在頁眉鬆動時為頁眉保留空間,例如在內容上使用佔位符元素或
margin-top
。 一個例外應該是不應該對 CLS 產生影響的 cookie 同意橫幅,但有時他們會這樣做:這取決於實施。 在這個 Twitter 線程中有一些有趣的策略和要點。對於可能包含大量文本的選項卡組件,您可以使用 CSS 網格堆棧來防止佈局變化。 通過將每個選項卡的內容放在同一個網格區域,並一次隱藏其中一個,我們可以確保容器始終採用較大元素的高度,因此不會發生佈局偏移。
啊,當然,如果列表下方有內容(例如頁腳),無限滾動和“加載更多”也會導致佈局變化。 要改進 CLS,請在用戶滾動到頁面的該部分之前為將要加載的內容保留足夠的空間,刪除頁腳或頁面底部可能因內容加載而被壓下的任何 DOM 元素。此外,為首屏內容預取數據和圖像,這樣當用戶滾動到那麼遠時,它就已經存在了。 您也可以使用 react-window 之類的列表虛擬化庫來優化長列表(感謝 Addy Osmani! )。
為確保包含回流的影響,請使用 Layout Instability API 測量佈局穩定性。 有了它,您可以計算 Cumulative Layout Shift ( CLS ) 分數並將其作為測試要求包含在內,因此每當出現回歸時,您都可以對其進行跟踪和修復。
為了計算佈局偏移分數,瀏覽器查看視口大小和兩個渲染幀之間的視口中不穩定元素的移動。 理想情況下,分數將接近
0
。 Milica Mihajlija 和 Philip Walton 就 CLS 是什麼以及如何測量它提供了一個很棒的指南。 這是衡量和維護感知性能並避免中斷的良好起點,尤其是對於關鍵業務任務。快速提示:要了解導致 DevTools 中佈局變化的原因,您可以在性能面板的“體驗”下探索佈局變化。
獎勵:如果您想減少重排和重繪,請查看 Charis Theodoulou 的《最小化 DOM 重排/佈局抖動指南》和 Paul Irish 的強制佈局/重排的列表以及 CSSTriggers.com,這是一個關於觸發佈局、繪製的 CSS 屬性的參考表和合成。
網絡和 HTTP/2
- 是否啟用了 OCSP 裝訂?
通過在您的服務器上啟用 OCSP 裝訂,您可以加快 TLS 握手。 在線證書狀態協議 (OCSP) 是作為證書撤銷列表 (CRL) 協議的替代方案而創建的。 這兩種協議都用於檢查 SSL 證書是否已被吊銷。但是,OCSP 協議不需要瀏覽器花時間下載然後搜索證書信息列表,因此減少了握手所需的時間。
- 您是否減少了 SSL 證書吊銷的影響?
在他關於“EV 證書的性能成本”的文章中,Simon Hearne 對常見證書進行了很好的概述,以及證書的選擇可能對整體性能產生的影響。正如 Simon 所寫,在 HTTPS 的世界中,有幾種類型的證書驗證級別用於保護流量:
- 域驗證(DV) 驗證證書請求者是否擁有該域,
- 組織驗證(OV) 驗證組織是否擁有該域,
- 擴展驗證(EV) 通過嚴格的驗證來驗證組織是否擁有該域。
需要注意的是,所有這些證書在技術方面都是相同的。 它們僅在這些證書中提供的信息和屬性上有所不同。
EV 證書既昂貴又耗時,因為它們需要人工審核證書並確保其有效性。 另一方面,DV 證書通常是免費提供的——例如由 Let's Encrypt——一個開放的、自動化的證書頒發機構,它很好地集成到許多託管服務提供商和 CDN 中。 事實上,在撰寫本文時,它為超過 2.25 億個網站 (PDF) 提供支持,儘管它僅佔頁面的 2.69%(在 Firefox 中打開)。
那麼有什麼問題呢? 問題是EV 證書不完全支持上面提到的 OCSP 裝訂。 雖然裝訂允許服務器與證書頒發機構檢查證書是否已被吊銷,然後將此信息添加(“裝訂”)到證書中,而無需裝訂客戶端必須完成所有工作,從而導致在 TLS 協商期間產生不必要的請求. 在連接不佳的情況下,這可能會導致顯著的性能成本(1000 毫秒以上)。
EV 證書對於 Web 性能來說並不是一個很好的選擇,而且它們對性能的影響要比 DV 證書大得多。 為獲得最佳 Web 性能,請始終提供 OCSP 裝訂的 DV 證書。 它們也比 EV 證書便宜得多,而且獲得的麻煩更少。 好吧,至少在 CRLite 可用之前。
注意:使用 QUIC/HTTP/3,值得注意的是 TLS 證書鍊是一個可變大小的內容,它在 QUIC 握手中支配字節數。 大小在幾百字節和超過 10 KB 之間變化。
因此,在 QUIC/HTTP/3 上保持 TLS 證書很小很重要,因為大型證書會導致多次握手。 此外,我們需要確保證書被壓縮,否則證書鏈將太大而無法容納在單個 QUIC 飛行中。
您可以在以下位置找到更多詳細信息和指向問題和解決方案的方法:
- EV 證書使 Web 變得緩慢且不可靠 作者 Aaron Peters,
- SSL 證書吊銷對 Web 性能的影響 by Matt Hobbs,
- Simon Hearne 的 EV 證書的性能成本,
- QUIC 握手是否需要快速壓縮? 通過帕特里克麥克馬納斯。
- 您採用 IPv6 了嗎?
因為我們的 IPv4 空間已經用完,而且主要移動網絡正在迅速採用 IPv6(美國幾乎達到了 50% 的 IPv6 採用閾值),所以最好將您的 DNS 更新為 IPv6,以防萬一。 只需確保跨網絡提供雙棧支持——它允許 IPv6 和 IPv4 同時運行。 畢竟,IPv6 不是向後兼容的。 此外,研究表明,由於鄰居發現 (NDP) 和路由優化,IPv6 使這些網站的速度提高了 10% 到 15%。 - 確保所有資產都通過 HTTP/2(或 HTTP/3)運行。
隨著谷歌在過去幾年中推動更安全的 HTTPS 網絡,切換到 HTTP/2 環境絕對是一項不錯的投資。 事實上,根據 Web Almanac,64% 的請求已經在 HTTP/2 上運行。重要的是要了解 HTTP/2 並不完美並且存在優先級問題,但它得到了很好的支持; 而且,在大多數情況下,你會更好。
提醒一句:HTTP/2 Server Push 正在從 Chrome 中刪除,因此如果您的實現依賴於 Server Push,您可能需要重新訪問它。 相反,我們可能正在研究 Early Hints,它已經作為實驗集成在 Fastly 中。
如果您仍然在 HTTP 上運行,最耗時的任務將是首先遷移到 HTTPS,然後調整您的構建過程以適應 HTTP/2 多路復用和並行化。 將 HTTP/2 引入 Gov.uk 是一個非常棒的案例研究,可以在此過程中通過 CORS、SRI 和 WPT 找到方法。 對於本文的其餘部分,我們假設您正在切換到或已經切換到 HTTP/2。
- 正確部署 HTTP/2。
同樣,通過 HTTP/2 提供資產可以受益於對迄今為止提供資產的方式進行部分改革。 您需要在打包模塊和並行加載許多小模塊之間找到一個很好的平衡點。 歸根結底,最好的請求仍然是沒有請求,但是,目標是在快速首次交付資產和緩存之間找到一個很好的平衡點。一方面,您可能希望避免完全連接資產,而不是將整個界面分解為許多小模塊,將它們作為構建過程的一部分進行壓縮並並行加載。 一個文件的更改不需要重新下載整個樣式表或 JavaScript。 它還最大限度地減少了解析時間,並使各個頁面的有效負載保持在較低水平。
另一方面,包裝仍然很重要。 通過使用許多小腳本,整體壓縮會受到影響,並且從緩存中檢索對象的成本會增加。 大包的壓縮將受益於字典重用,而單獨的小包則不會。 有標準的工作來解決這個問題,但現在還很遙遠。 其次,瀏覽器尚未針對此類工作流程進行優化。 例如,Chrome 會觸發與資源數量成線性關係的進程間通信 (IPC),因此包含數百個資源將產生瀏覽器運行時成本。
不過,您可以嘗試逐步加載 CSS。 事實上,in-body CSS 不再阻礙 Chrome 的渲染。 但是有一些優先級問題,所以它不是那麼簡單,但值得嘗試。
您可以使用 HTTP/2 連接合併,它允許您在受益於 HTTP/2 的同時使用域分片,但在實踐中實現這一點很困難,而且通常不被認為是好的做法。 此外,HTTP/2 和子資源完整性並不總是有效。
該怎麼辦? 好吧,如果你在 HTTP/2 上運行,發送大約6-10 個包似乎是一個不錯的折衷方案(對於舊版瀏覽器來說還不錯)。 試驗和測量以找到適合您網站的平衡點。
- 我們是否通過單個 HTTP/2 連接發送所有資產?
HTTP/2 的主要優點之一是它允許我們通過單個連接將資產發送到線路上。 然而,有時我們可能做錯了什麼——例如有一個 CORS 問題,或者錯誤配置了crossorigin
屬性,所以瀏覽器將被迫打開一個新連接。要檢查所有請求是否使用單個 HTTP/2 連接,或者是否配置錯誤,請啟用 DevTools → Network 中的“Connection ID”列。 例如,在這裡,所有請求共享相同的連接 (286) — 除了 manifest.json,它打開一個單獨的連接 (451)。
- 您的服務器和 CDN 是否支持 HTTP/2?
不同的服務器和 CDN(仍然)以不同的方式支持 HTTP/2。 使用 CDN 比較來檢查您的選項,或快速查看您的服務器的性能以及您期望支持的功能。請參閱 Pat Meenan 關於 HTTP/2 優先級(視頻)的令人難以置信的研究,並測試服務器對 HTTP/2 優先級的支持。 根據 Pat 的說法,建議啟用 BBR 擁塞控制並將
tcp_notsent_lowat
設置為 16KB,以便 HTTP/2 優先級在 Linux 4.9 內核及更高版本上可靠地工作(感謝 Yoav! )。 Andy Davies 對跨瀏覽器、CDN 和雲託管服務的 HTTP/2 優先級進行了類似的研究。使用它時,請仔細檢查您的內核是否支持 TCP BBR,並在可能的情況下啟用它。 它目前用於 Google Cloud Platform、Amazon Cloudfront、Linux(例如 Ubuntu)。
- 是否正在使用 HPACK 壓縮?
如果您使用的是 HTTP/2,請仔細檢查您的服務器是否為 HTTP 響應標頭實施 HPACK 壓縮,以減少不必要的開銷。 一些 HTTP/2 服務器可能不完全支持該規範,HPACK 就是一個例子。 H2spec 是一個很棒的(如果技術上非常詳細)工具來檢查它。 HPACK 的壓縮算法令人印象深刻,而且它確實有效。 - 確保服務器上的安全性是防彈的。
HTTP/2 的所有瀏覽器實現都通過 TLS 運行,因此您可能希望避免出現安全警告或頁面上的某些元素不起作用。 仔細檢查您的安全標頭是否設置正確,消除已知漏洞,並檢查您的 HTTPS 設置。此外,確保所有外部插件和跟踪腳本都是通過 HTTPS 加載的,跨站點腳本是不可能的,並且 HTTP 嚴格傳輸安全標頭和內容安全策略標頭都已正確設置。
- 您的服務器和 CDN 是否支持 HTTP/3?
雖然 HTTP/2 為 Web 帶來了許多顯著的性能改進,但它也留下了相當多的改進空間——尤其是 TCP 中的線頭阻塞,這在具有嚴重數據包丟失的慢速網絡上很明顯。 HTTP/3 正在徹底解決這些問題(文章)。為了解決 HTTP/2 問題,IETF 與 Google、Akamai 和其他公司一起,一直在研究一種新協議,該協議最近被標準化為 HTTP/3。
Robin Marx 已經很好地解釋了 HTTP/3,下面的解釋是基於他的解釋。 就其核心而言,HTTP/3 在功能方面與 HTTP/2 非常相似,但在底層它的工作方式卻大不相同。 HTTP/3 提供了許多改進:更快的握手、更好的加密、更可靠的獨立流、更好的加密和流控制。 一個顯著的區別是 HTTP/3 使用 QUIC 作為傳輸層,QUIC 數據包封裝在 UDP 圖而不是 TCP 之上。
QUIC 將 TLS 1.3 完全集成到協議中,而在 TCP 中它是分層的。 在典型的 TCP 堆棧中,我們有一些往返時間的開銷,因為 TCP 和 TLS 需要自己進行單獨的握手,但是使用 QUIC 可以將它們組合在一起並在一次往返中完成。 由於 TLS 1.3 允許我們為後續連接設置加密密鑰,從第二個連接開始,我們已經可以在第一次往返中發送和接收應用層數據,稱為“0-RTT”。
此外,HTTP/2 的標頭壓縮算法及其優先級系統也被完全重寫。 此外,QUIC 通過每個 QUIC 數據包標頭中的連接 ID 支持從 Wi-Fi 到蜂窩網絡的連接遷移。 大多數實現都是在用戶空間完成的,而不是內核空間(就像 TCP 一樣),所以我們應該期待協議在未來不斷發展。
這一切都會有很大的不同嗎? 可能是的,尤其是對移動設備的加載時間有影響,而且對我們如何向最終用戶提供資產也有影響。 在 HTTP/2 中,多個請求共享一個連接,而在 HTTP/3 中,請求也共享一個連接但獨立流式傳輸,因此丟棄的數據包不再影響所有請求,只會影響一個流。
這意味著,雖然使用一個大型 JavaScript 包,當一個流暫停時資產的處理速度會減慢,但當多個文件並行流 (HTTP/3) 時,影響將不那麼顯著。 所以包裝仍然很重要。
HTTP/3 仍在進行中。 Chrome、Firefox 和 Safari 已經有了實現。 一些 CDN 已經支持 QUIC 和 HTTP/3。 2020 年底,Chrome 開始部署 HTTP/3 和 IETF QUIC,實際上所有 Google 服務(Google Analytics、YouTube 等)都已經在 HTTP/3 上運行。 LiteSpeed Web Server 支持 HTTP/3,但 Apache、nginx 或 IIS 都不支持它,但它很可能在 2021 年迅速改變。
底線:如果您可以選擇在服務器和 CDN 上使用 HTTP/3,那麼這樣做可能是一個非常好的主意。 主要好處將來自同時獲取多個對象,尤其是在高延遲連接上。 我們還不確定,因為在該領域沒有太多研究,但初步結果非常有希望。
如果您想更深入地了解協議的細節和優勢,這裡有一些很好的起點來檢查:
- HTTP/3 Explained,一個記錄 HTTP/3 和 QUIC 協議的協作努力。 有多種語言版本,也有 PDF 格式。
- Daniel Stenberg 使用 HTTP/3 提升 Web 性能。
- 與 Robin Marx 合作的 QUIC 學術指南介紹了 QUIC 和 HTTP/3 協議的基本概念,解釋了 HTTP/3 如何處理線頭阻塞和連接遷移,以及 HTTP/3 如何被設計為常青樹(感謝Simon !)。
- 您可以在 HTTP3Check.net 上檢查您的服務器是否在 HTTP/3 上運行。
測試和監控
- 您是否優化了審計工作流程?
這聽起來可能沒什麼大不了的,但是觸手可及的正確設置可能會為您節省大量測試時間。 考慮使用 Tim Kadlec 的 Alfred Workflow for WebPageTest 向 WebPageTest 的公共實例提交測試。 事實上,WebPageTest 有許多晦澀難懂的功能,因此請花時間學習如何閱讀 WebPageTest 瀑布視圖圖表以及如何閱讀 WebPageTest 連接視圖圖表以更快地診斷和解決性能問題。您還可以從 Google 電子表格驅動 WebPageTest,並使用 Lighthouse CI 將可訪問性、性能和 SEO 分數整合到您的 Travis 設置中,或者直接整合到 Webpack 中。
看看最近發布的 AutoWebPerf,這是一個模塊化工具,可以自動收集來自多個來源的性能數據。 例如,我們可以在您的關鍵頁面上設置每日測試,以捕獲來自 CrUX API 的現場數據和來自 PageSpeed Insights 的 Lighthouse 報告的實驗室數據。
如果你需要快速調試一些東西,但你的構建過程似乎非常慢,請記住“空白刪除和符號修改佔大多數 JavaScript 縮小代碼大小減少的 95%——而不是複雜的代碼轉換。你可以只需禁用壓縮即可將 Uglify 構建速度提高 3 到 4 倍。”
- 您是否在代理瀏覽器和舊版瀏覽器中進行了測試?
在 Chrome 和 Firefox 中進行測試是不夠的。 查看您的網站在代理瀏覽器和舊版瀏覽器中的工作方式。 例如,UC 瀏覽器和 Opera Mini 在亞洲擁有重要的市場份額(在亞洲高達 35%)。 測量您感興趣的國家/地區的平均互聯網速度,以避免未來出現重大意外。 使用網絡限制進行測試,並模擬高 DPI 設備。 BrowserStack 非常適合在遠程真實設備上進行測試,並且還可以在您的辦公室中至少使用一些真實設備來補充它——這是值得的。
- 您是否測試過 404 頁面的性能?
通常,當涉及到 404 頁時,我們不會三思而後行。 畢竟,當客戶端請求服務器上不存在的頁面時,服務器將響應 404 狀態代碼和相關的 404 頁面。 它沒有那麼多,不是嗎?404 響應的一個重要方面是發送到瀏覽器的實際響應正文大小。 根據 Matt Hobbs 對 404 頁面的研究,絕大多數 404 響應來自丟失的網站圖標、WordPress 上傳請求、損壞的 JavaScript 請求、清單文件以及 CSS 和字體文件。 每次客戶請求不存在的資產時,他們都會收到 404 響應——而且該響應通常是巨大的。
確保檢查和優化 404 頁面的緩存策略。 我們的目標是僅在瀏覽器需要 HTML 響應時才向瀏覽器提供 HTML,並為所有其他響應返回一個小的錯誤負載。 根據 Matt 的說法,“如果我們在源之前放置一個 CDN,我們就有機會在 CDN 上緩存 404 頁面響應。這很有用,因為沒有它,點擊 404 頁面可能會被用作 DoS 攻擊向量,通過強制源服務器響應每個 404 請求,而不是讓 CDN 以緩存版本響應。”
404 錯誤不僅會損害您的性能,而且還會影響流量,因此最好在您的 Lighthouse 測試套件中包含 404 錯誤頁面,並隨著時間的推移跟踪其分數。
- 您是否測試過 GDPR 同意提示的性能?
在 GDPR 和 CCPA 時代,依靠第三方為歐盟客戶提供選擇加入或退出跟踪的選項已變得很普遍。 但是,與任何其他第三方腳本一樣,它們的性能可能會對整個性能工作產生相當大的破壞性影響。當然,實際同意可能會改變腳本對整體性能的影響,因此,正如 Boris Schapira 所指出的,我們可能想要研究一些不同的 Web 性能配置文件:
- 完全拒絕同意,
- 同意被部分拒絕,
- 完全同意。
- 用戶尚未對同意提示採取行動(或提示被內容阻止程序阻止),
通常 cookie 同意提示不應該對 CLS 產生影響,但有時會產生影響,因此請考慮使用免費和開源選項 Osano 或 cookie-consent-box。
一般來說,值得研究彈出窗口的性能,因為您需要確定鼠標事件的水平或垂直偏移量,並正確定位彈出窗口相對於錨點的位置。 Noam Rosenthal 在文章 Web 性能案例研究:維基百科頁面預覽(也可作為視頻和會議紀要)中分享了 Wikimedia 團隊的學習成果。
- 您是否保留了性能診斷 CSS?
雖然我們可以包括各種檢查以確保部署非性能代碼,但通常快速了解一些可以輕鬆解決的容易實現的成果是有用的。 為此,我們可以使用 Tim Kadlec 出色的性能診斷 CSS(靈感來自 Harry Roberts 的片段,該片段突出顯示延遲加載的圖像、未調整大小的圖像、舊格式圖像和同步腳本。例如,您可能希望確保首屏上方的圖像不會被延遲加載。 您可以根據需要自定義片段,例如突出顯示未使用的網絡字體,或檢測圖標字體。 一個很棒的小工具,可以確保在調試過程中可以看到錯誤,或者只是快速審核當前項目。
/* Performance Diagnostics CSS */ /* via Harry Roberts. https://twitter.com/csswizardry/status/1346477682544951296 */ img[loading=lazy] { outline: 10px solid red; }
- 您是否測試過對可訪問性的影響?
當瀏覽器開始加載頁面時,它會構建一個 DOM,如果有像屏幕閱讀器這樣的輔助技術在運行,它還會創建一個可訪問性樹。 然後,屏幕閱讀器必須查詢可訪問性樹以檢索信息並將其提供給用戶——有時是默認的,有時是按需提供的。 有時這需要時間。在談論快速交互時間時,通常我們指的是用戶可以通過單擊或點擊鏈接和按鈕與頁面交互的速度指標。 屏幕閱讀器的上下文略有不同。 在這種情況下,快速交互時間意味著屏幕閱讀器可以在給定頁面上宣布導航並且屏幕閱讀器用戶可以實際敲擊鍵盤進行交互之前經過了多少時間。
Leonie Watson 就可訪問性性能發表了令人大開眼界的演講,特別是緩慢加載對屏幕閱讀器公告延遲的影響。 屏幕閱讀器習慣於快節奏的公告和快速導航,因此可能比有視力的用戶更沒有耐心。
使用 JavaScript 進行的大頁面和 DOM 操作將導致屏幕閱讀器公告延遲。 幾乎每個平台(Jaws、NVDA、Voiceover、Narrator、Orca)上都有一個相當未開發的領域,可以使用一些注意力和測試作為屏幕閱讀器。
- 是否設置了持續監控?
擁有 WebPagetest 的私有實例總是有利於快速和無限制的測試。 但是,具有自動警報的持續監控工具(如 Sitespeed、Calibre 和 SpeedCurve)可以讓您更詳細地了解您的表現。 設置您自己的用戶計時標記來衡量和監控特定於業務的指標。 此外,考慮添加自動性能回歸警報以監控隨時間的變化。研究使用 RUM 解決方案來監控性能隨時間的變化。 對於自動化的類似單元測試的負載測試工具,您可以使用 k6 及其腳本 API。 另外,請查看 SpeedTracker、Lighthouse 和 Calibre。
快速獲勝
這個列表非常全面,完成所有優化可能需要相當長的時間。 那麼,如果你只有 1 小時的時間來獲得顯著的改進,你會怎麼做? 讓我們把它歸結為17 種容易獲得的果實。 顯然,在開始之前和完成後,測量結果,包括最大內容繪製和 3G 和電纜連接上的交互時間。
- 衡量現實世界的體驗並設定適當的目標。 目標是比你最快的競爭對手至少快 20%。 保持在最大內容繪製 < 2.5 秒內,首次輸入延遲 < 100 毫秒,在慢速 3G 上交互時間 < 5 秒,重複訪問,TTI < 2 秒。 至少優化首次內容繪製和交互時間。
- 使用 Squoosh、mozjpeg、guetzli、pingo 和 SVGOMG 優化圖像,並使用圖像 CDN 提供 AVIF/WebP。
- 為您的主模板準備關鍵的 CSS,並將它們內聯在每個模板的
<head>
中。 對於 CSS/JS,在最大的關鍵文件大小預算內運行。 170KB gzipped(0.7MB 解壓)。 - 修剪、優化、延遲和延遲加載腳本。 投資捆綁器的配置以消除冗餘並檢查輕量級替代方案。
- 始終自託管您的靜態資產,並且始終更喜歡自託管第三方資產。 限制第三方腳本的影響。 使用外觀,在交互中加載小部件並註意防閃爍片段。
- 選擇框架時要有選擇性。 對於單頁應用程序,識別關鍵頁面並靜態提供它們,或者至少預渲染它們,並在組件級別使用漸進式水合併在交互時導入模塊。
- 單獨的客戶端渲染對於性能來說並不是一個好的選擇。 如果您的頁面沒有太大變化,請預渲染,如果可以,請推遲啟動框架。 如果可能,請使用流式服務器端渲染。
- 僅向具有
<script type="module">
和 module/nomodule 模式的舊版瀏覽器提供舊版代碼。 - 嘗試重新組合 CSS 規則並測試 in-body CSS。
- 添加資源提示以通過更快
dns-lookup
、preconnect
、prefetch
、preload
和prerender
加快交付。 - 子集網絡字體並異步加載它們,並利用 CSS 中的
font-display
進行快速首次渲染。 - 檢查 HTTP 緩存標頭和安全標頭是否設置正確。
- 在服務器上啟用 Brotli 壓縮。 (如果這不可能,至少確保啟用 Gzip 壓縮。)
- 只要您的服務器在 Linux 內核版本 4.9+ 上運行,就啟用 TCP BBR 擁塞。
- 如果可能,啟用 OCSP 裝訂和 IPv6。 始終提供 OCSP 裝訂的 DV 證書。
- 為 HTTP/2 啟用 HPACK 壓縮,如果可用,請移至 HTTP/3。
- 在 Service Worker 緩存中緩存字體、樣式、JavaScript 和圖像等資產。
下載清單(PDF,Apple Pages)
記住這個清單,你應該為任何類型的前端性能項目做好準備。 隨意下載清單的可打印 PDF 以及可編輯的 Apple Pages 文檔,以根據您的需要自定義清單:
- 下載清單 PDF (PDF, 166 KB)
- 在 Apple Pages (.pages, 275 KB) 中下載清單
- 下載 MS Word 中的清單 (.docx, 151 KB)
如果您需要替代方案,您還可以查看 Dan Rublic 的前端清單、Jon Yablonski 的“Designer's Web Performance Checklist”和 FrontendChecklist。
我們走吧!
一些優化可能超出了您的工作或預算範圍,或者考慮到您必須處理的遺留代碼可能只是過度殺傷力。 沒關係! 將此清單用作一般(希望是全面的)指南,並創建您自己的適用於您的上下文的問題列表。 但最重要的是,在優化之前測試和測量您自己的項目以識別問題。 祝大家2021年業績好!
非常感謝 Guy Podjarny、Yoav Weiss、Addy Osmani、Artem Denysov、Denys Mishunov、Ilya Pukhalski、Jeremy Wagner、Colin Bendell、Mark Zeman、Patrick Meenan、Leonardo Losoviz、Andy Davies、Rachel Andrew、Anselm Hannemann、Barry Pollard、Patrick哈曼、吉迪恩·派澤、安迪·戴維斯、瑪麗亞·普羅斯維尼娜、蒂姆·卡德萊克、雷伊·班戈、馬蒂亞斯·奧特、彼得·鮑耶、菲爾·沃爾頓、瑪麗安娜·佩拉爾塔、佩皮恩·森德斯、馬克·諾丁漢、讓·皮埃爾·文森特、菲利普·泰利斯、瑞恩·湯森、英格麗·褒曼、穆罕默德·侯賽因SH、Jacob Groß、Tim Swalling、Bob Visser、Kev Adamson、Adir Amsalem、Aleksey Kulikov 和 Rodney Rehm 對本文的審閱,以及我們出色的社區分享了從性能優化工作中獲得的技術和經驗教訓,供大家使用. 你真的很牛逼!