我們如何改進我們的核心 Web Vitals(案例研究)
已發表: 2022-03-10去年,Google 開始強調 Core Web Vitals 的重要性,以及它們如何反映一個人在訪問網絡站點時的真實體驗。 性能是我們公司 Instant Domain Search 的核心功能——它就在名稱中。 想像一下,當我們發現我們的生命體徵分數對很多人來說都不是很好時,我們會感到驚訝。 我們快速的計算機和光纖互聯網掩蓋了真實人們在我們網站上的體驗。 不久之後,我們的 Google Search Console 中大量的紅色“差”和黃色“需要改進”通知需要我們關注。 Entropy 贏了,我們必須想辦法清理垃圾——讓我們的網站更快。
我在 2005 年創立了 Instant Domain Search,並在我在一家 Y Combinator 公司(Snipshot,W06)工作期間將其作為副業,然後在 Facebook 擔任軟件工程師。 我們最近成長為一個主要位於加拿大維多利亞的小型團隊,我們正在處理長期積壓的新功能和性能改進。 我們糟糕的網絡生命體徵得分和迫在眉睫的 Google 更新使我們專注於發現和解決這些問題。
當該站點的第一個版本發佈時,我使用 PHP、MySQL 和 XMLHttpRequest 構建了它。 Internet Explorer 6 得到完全支持,Firefox 的份額越來越大,而 Chrome 距離發布還有幾年的時間。 隨著時間的推移,我們通過各種靜態站點生成器、JavaScript 框架和服務器技術不斷發展。 我們當前的前端堆棧是使用 Next.js 提供的 React 和內置 Rust 的後端服務來回答我們的域名搜索。 我們嘗試遵循最佳實踐,通過 CDN 提供盡可能多的服務,盡可能避免使用第三方腳本,並使用簡單的 SVG 圖形而不是位圖 PNG。 這還不夠。
Next.js 讓我們可以在 React 和 TypeScript 中構建我們的頁面和組件。 與 VS Code 搭配使用時,開發體驗非常棒。 Next.js 通常通過將 React 組件轉換為靜態 HTML 和 CSS 來工作。 這樣,可以從 CDN 提供初始內容,然後 Next 可以“水合”頁面以使元素動態化。 一旦頁面被水合,我們的網站就會變成一個單頁應用程序,人們可以在其中搜索和生成域名。 我們不依賴 Next.js 做很多服務器端工作,我們的大部分內容都靜態導出為 HTML、CSS 和 JavaScript,以便從 CDN 提供服務。
當有人開始搜索域名時,我們將頁面內容替換為搜索結果。 為了使搜索盡可能快,前端直接查詢我們的 Rust 後端,該後端針對域查找和建議進行了高度優化。 我們可以立即回答許多查詢,但對於某些 TLD,我們需要執行較慢的 DNS 查詢,這可能需要一兩秒才能解決。 當其中一些較慢的查詢得到解決時,我們將使用任何新信息更新 UI。每個人的結果頁面都不同,我們很難準確預測每個人對網站的體驗。
Chrome DevTools 非常出色,是解決性能問題時的好起點。 性能視圖準確地顯示 HTTP 請求何時發出、瀏覽器在何處花費時間評估 JavaScript 等等:
Google 將使用三個核心 Web Vitals 指標來幫助在即將到來的搜索算法更新中對網站進行排名。 Google 根據真實用戶在網站上的 LCP、FID 和 CLS 分數將體驗分為“好”、“需要改進”和“差”:
- LCP或最大內容繪製定義了最大內容元素變為可見所需的時間。
- FID或 First Input Delay 與站點對交互的響應有關——界面中的輕擊、單擊或按鍵與頁面響應之間的時間。
- CLS或 Cumulative Layout Shift 跟踪元素在沒有鍵盤或單擊事件等操作的情況下如何在頁面上移動或移動。
Chrome 設置為跟踪所有登錄 Chrome 用戶的這些指標,並將總結客戶在網站上的體驗的匿名統計數據發送回谷歌進行評估。 這些分數可通過 Chrome 用戶體驗報告訪問,並在您使用 PageSpeed Insights 工具檢查 URL 時顯示。 這些分數代表在過去 28 天內訪問該 URL 的人的第 75 個百分位體驗。 這是他們將用來幫助對更新中的站點進行排名的數字。
第 75 個百分位 (p75) 指標在性能目標方面取得了合理的平衡。 例如,取平均值會隱藏人們的許多不良經歷。 中位數或第 50 個百分位數 (p50) 意味著使用我們產品的人中有一半的體驗更差。 另一方面,第 95 個百分位 (p95) 很難構建,因為它在連接不完整的舊設備上捕獲了太多極端異常值。 我們認為基於 75% 的評分是一個公平的標準。
為了控制我們的分數,我們首先求助於 Lighthouse,以獲取 Chrome 中內置的一些優秀工具,並託管在 web.dev/measure/ 和 PageSpeed Insights 上。 這些工具幫助我們找到了我們網站的一些廣泛的技術問題。 我們看到 Next.js 捆綁我們的 CSS 並減慢了我們的初始渲染時間,這影響了我們的 FID。 第一個輕鬆的勝利來自一個實驗性的 Next.js 功能,optimizeCss,它有助於顯著提高我們的總體性能得分。
Lighthouse 還發現了一個緩存錯誤配置,導致我們的一些靜態資產無法從我們的 CDN 提供服務。 我們託管在 Google Cloud Platform 上,Google Cloud CDN 要求 Cache-Control 標頭包含“public”。 Next.js 不允許您配置它發出的所有標頭,因此我們必須通過將 Next.js 服務器放在 Caddy 後面來覆蓋它們,Caddy 是用 Go 實現的輕量級 HTTP 代理服務器。 我們還藉此機會通過現代瀏覽器中相對較新的 stale-while-revalidate 支持確保我們能夠提供我們所能提供的服務,這允許 CDN 在後台異步從源(我們的 Next.js 服務器)獲取內容。
從 npm 向產品中添加幾乎所有需要的東西很容易——也許太容易了。 捆綁大小的增長不需要很長時間。 大包在慢速網絡上下載需要更長的時間,而且 75% 的手機會花費大量時間阻塞主 UI 線程,同時它會嘗試理解它剛剛下載的所有代碼。 我們喜歡 BundlePhobia,它是一個免費工具,可以顯示一個 npm 包將添加到你的包中的依賴項和字節數。 這導致我們用更簡單的 CSS 過渡消除或替換了許多由 react-spring 驅動的動畫:
通過使用 BundlePhobia 和 Lighthouse,我們發現第三方錯誤記錄和分析軟件對我們的包大小和加載時間有很大貢獻。 我們刪除了這些工具,並用我們自己的客戶端日誌記錄替換了這些工具,這些日誌記錄利用了現代瀏覽器 API,如 sendBeacon 和 ping。 我們將日誌記錄和分析發送到我們自己的 Google BigQuery 基礎架構,在那裡我們可以比任何現成的工具提供的更詳細地回答我們關心的問題。 這也消除了許多第三方 cookie,使我們能夠更好地控制從客戶端發送日誌數據的方式和時間。
我們的 CLS 分數仍有最大的提升空間。 谷歌計算 CLS 的方式很複雜——給你一個最大的“會話窗口”,間隔 1 秒,從初始頁面加載或鍵盤或點擊交互開始的 5 秒,以完成在網站上的移動. 如果您有興趣更深入地閱讀該主題,這裡有關於該主題的精彩指南。 這會懲罰在您登陸網站後出現的許多類型的覆蓋和彈出窗口。 例如,當您開始滾動過去的廣告以到達內容時,可能會出現轉移內容或追加銷售的廣告。 本文很好地解釋了 CLS 分數的計算方式及其背後的原因。
我們從根本上反對這種數字混亂,所以我們很驚訝地看到谷歌堅持我們有多大的改進空間。 Chrome 有一個內置的 Web Vitals 覆蓋,您可以使用命令菜單“顯示核心 Web Vitals 覆蓋”來訪問它。 為了準確了解 Chrome 在其 CLS 計算中考慮了哪些元素,我們發現 Chrome Web Vitals 擴展在設置中的“控制台日誌記錄”選項更有幫助。 啟用後,此插件會顯示您當前頁面的 LCP、FID 和 CLS 分數。 從控制台中,您可以準確地看到頁面上的哪些元素與這些分數相關聯。 我們的 CLS 分數有最大的提升空間。
在這三個指標中,CLS 是唯一一個在您與頁面交互時累積的指標。 Web Vitals 擴展有一個日誌記錄選項,可以在您與產品交互時準確顯示導致 CLS 的元素。 當我們在 Smashing Magazine 的主頁上滾動時,觀察 CLS 指標是如何增加的:
隨著時間的推移,Google 將繼續調整其計算 CLS 的方式,因此請務必關注 Google 的 Web 開發博客了解最新情況。 使用 Chrome Web Vitals 擴展等工具時,啟用 CPU 和網絡節流以獲得更真實的體驗非常重要。 您可以使用開發人員工具通過模擬移動 CPU 來做到這一點。
跟踪從一次部署到下一次部署進度的最佳方法是像 Google 一樣衡量頁面體驗。 如果您設置了 Google Analytics,一個簡單的方法是安裝 Google 的 web-vitals 模塊並將其連接到 Google Analytics。 這可以粗略衡量您的進度,並使其在 Google Analytics(分析)儀表板中可見。
這是我們碰壁的地方。 我們可以看到我們的 CLS 分數,雖然我們已經顯著提高了它,但我們還有工作要做。 我們的 CLS 分數大約為 0.23,我們需要將其降低到 0.1 以下,最好降到 0。不過,此時,我們無法找到確切的信息來告訴我們哪些頁面上的哪些組件仍然影響分數。 我們可以看到 Chrome 在他們的 Core Web Vitals 工具中暴露了很多細節,但是日誌聚合器拋棄了最重要的部分:究竟是哪個頁面元素導致了問題。
為了捕獲我們需要的所有細節,我們構建了一個無服務器函數來從瀏覽器中捕獲 Web Vitals 數據。 由於我們不需要對數據運行實時查詢,我們將其流式傳輸到 Google BigQuery 的流式 API 中進行存儲。 這種架構意味著我們可以廉價地捕獲我們可以生成的盡可能多的數據點。
在使用 Web Vitals 和 BigQuery 學習了一些經驗之後,我們決定捆綁此功能並將這些工具作為開源工具發佈在 vitals.dev。
使用 Instant Vitals 是一種快速開始在 BigQuery 中跟踪您的 Web Vitals 分數的方法。 以下是我們創建的 BigQuery 表架構示例:
與 Instant Vitals 集成很容易。 您可以通過與客戶端庫集成來開始將數據發送到您的後端或無服務器功能:
import { init } from "@instantdomain/vitals-client"; init({ endpoint: "/api/web-vitals" });
然後,在您的服務器上,您可以與服務器庫集成以完成電路:
import fs from "fs"; import { init, streamVitals } from "@instantdomain/vitals-server"; // Google libraries require service key as path to file const GOOGLE_SERVICE_KEY = process.env.GOOGLE_SERVICE_KEY; process.env.GOOGLE_APPLICATION_CREDENTIALS = "/tmp/goog_creds"; fs.writeFileSync( process.env.GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_SERVICE_KEY ); const DATASET_; init({ datasetId: DATASET_ID }).then().catch(console.error); // Request handler export default async (req, res) => { const body = JSON.parse(req.body); await streamVitals(body, body.name); res.status(200).end(); };
只需使用請求正文和指標名稱調用streamVitals
即可將指標發送到 BigQuery。 該庫將為您處理創建數據集和表。
在收集了一天的數據後,我們像這樣運行這個查詢:
SELECT `<project_name>.web_vitals.CLS`.Value, Node FROM `<project_name>.web_vitals.CLS` JOIN UNNEST(Entries) AS Entry JOIN UNNEST(Entry.Sources) WHERE Node != "" ORDER BY value LIMIT 10
此查詢產生如下結果:
價值 | 節點 |
---|---|
4.6045324800736724E-4 | /html/body/div[1]/main/div/div/div[2]/div/div/blockquote |
7.183070668914928E-4 | /html/body/div[1]/header/div/div/header/div |
0.031002668277977697 | /html/body/div[1]/footer |
0.035830703317463526 | /html/body/div[1]/main/div/div/div[2] |
0.035830703317463526 | /html/body/div[1]/footer |
0.035830703317463526 | /html/body/div[1]/main/div/div/div[2] |
0.035830703317463526 | /html/body/div[1]/main/div/div/div[2] |
0.035830703317463526 | /html/body/div[1]/footer |
0.035830703317463526 | /html/body/div[1]/footer |
0.03988482067913317 | /html/body/div[1]/footer |
這向我們展示了哪些頁面上的哪些元素對 CLS 影響最大。 它為我們的團隊創建了一個清單來調查和修復。 在即時域搜索上,事實證明,緩慢或不良的移動連接將需要 500 多毫秒才能加載我們的一些搜索結果。 對於這些用戶來說,CLS 最糟糕的貢獻者之一實際上是我們的頁腳。
佈局移位分數是根據元素移動的大小以及移動的距離來計算的。 在我們的搜索結果視圖中,如果設備花費超過一定時間來接收和呈現搜索結果,結果視圖將折疊到zero-height
,從而使頁腳進入視圖。 當結果出現時,他們將頁腳推回頁面底部。 移動這麼遠的一個大 DOM 元素為我們的 CLS 分數增加了很多。 為了正確解決這個問題,我們需要重新構建搜索結果的收集和呈現方式。 我們決定只刪除搜索結果視圖中的頁腳,作為一種快速破解方法,它可以阻止它在慢速連接上彈跳。
我們現在定期審查這份報告,以跟踪我們如何改進——並在我們前進的過程中利用它來對抗不斷下降的結果。 我們見證了對我們網站上新推出的功能和產品的額外關注的價值,並實施了一致的檢查,以確保核心生命體徵有利於我們的排名。 我們希望通過分享 Instant Vitals,我們也可以幫助其他開發人員解決他們的 Core Web Vitals 分數。
Google 提供了內置於 Chrome 中的出色性能工具,我們使用它們來查找和修復許多性能問題。 我們了解到,谷歌提供的現場數據很好地總結了我們的 p75 進展,但沒有可操作的細節。 我們需要準確找出導致佈局偏移和輸入延遲的 DOM 元素。 一旦我們開始收集我們自己的字段數據(使用 XPath 查詢),我們就能夠識別特定的機會來改善每個人在我們網站上的體驗。 通過一些努力,我們將真實世界的 Core Web Vitals 字段分數降低到可接受的範圍內,為 6 月的頁面體驗更新做準備。 我們很高興看到這些數字向右下方下降!