Layer0是開放原始碼電子商務PWA框架React Storefront的主要貢獻者。 今年早些時候,我們做出了許多新功能和優化 React Storefront v7其中兩個最重要的是向Next.js的轉移和刪除了幾個關鍵依賴項(例如MobX),以支持React的新內置狀態管理功能,如useState hook和Context API。 這導致瀏攬器綑綁包大小大約減半。
這是當時的一個很好的成績,有助於提高從80年代到90年代的典型React Storefront應用程式的性能得分(由PageSpeed Insights (PSI)測量)。 在上下文方面,在Lighthouse v5.7上,83分以上的得分超過了500個電子商務網站中的99%。 我們沒有意識到,在未來幾個月內,當Lighthouse v6.0像炸彈一樣跌落並消除每個人的性能分數時,減少綑綁包的數量將會是多麼重要。
瞭解Lighthouse得分在PSI上測得的主要電子商務網站在v6.0被丟棄時如何變化:
Lighthouse 6.0中的新指標:TBT,LCP和CLS
Lighthouse v6.0引入了幾個新的感知速度指標,並重新定義了總體指標如何影響頁面的Lighthouse性能得分。燈塔5.7號 | 重量 | 燈塔6.0號 | 重量 |
---|---|---|---|
首款Contentful油漆(FCP) | 20% | 首款Contentful油漆(FCP) | 15% |
速度指數(SI) | 27% | 速度指數(SI) | 15% |
第一個有意義的塗料(FMP) | 7% | 最大Contenful油漆(LCP) | 25% |
第一個CPU閒置(FCI) | 13% | 總阻塞時間(TBT) | 15% |
交互時間(TTI) | 33% | 交互時間(TTI) | 15% |
- | - | 累計版面配置偏移(CLS) | 5% |
總阻塞時間(TBT)
Total Blocking Time (總阻塞時間)是Lighthouse v6.0中包含的一個新指標,用於測量JavaScript的分析和執行在頁面載入期間阻塞主線程的時間。 這一標準非常嚴厲地對待現代,繁重的JavaScript SPA/PWA。 儘管React Storefront 50的綑綁包大小縮減了75%,但我們發現Lighthouse v6.0產品頁面的性能分數下降了20至30點,這主要是由於包含TBT作為影響整體性能分數25 %的指標。
如果您使用的是支援伺服器端渲染的異型框架(如Next.js),則TBT主要由束大小和水合時間決定。 簡而言之,改進TBT的唯一方法是消除依賴性,優化組件,或通過使用更少的組件使站點變得更簡單。
最大Contentful塗料(LCP)
LCP是一種新的指標,其比重為Lighthouse v6.0總分的25%。 LCP旨在通過觀察最大內容元素完成油漆所需的時間來量化用戶對初始頁面載入性能的看法。 對於大多數網站,特別是在電子商務網站,最大的內容元素是主人翁形象。 對於React Storefront應用程式中的商品頁面,最大的內容元素是主商品圖片。 如果您不確定網站上有哪些元素,PSI將告訴您:
累計版面配置偏移(CLS)
累計版面配置偏移量會測量初始頁面載入期間頁面配置偏移量。 布局偏移最常見的原因是圖像,當從網路下載數據後,圖像會隨著圖像的大小而調整元素的大小而推動圖像周圍的元素。 通常可以通過在每個圖像載入前為其保留空間來完全消除布局偏移。 幸運的是,React Storefront的Image組件已經做到了這一點,因此React Storefront入門應用程式開箱即用的完美CLS分數為0。 應該注意的是,CLS質量差的其他常見問題是頁面初始油漆後出現的橫幅和彈出窗口。 使用者討厭他們,現在Lighthouse也是如此。我們如何優化React Storefront for Lighthouse v6.0
當我們使用PageSpeed Insights首次測試React Storefront入門應用程式在Lighthouse v6.0上的產品頁面時,得分低於60:為了提高得分,我們首先將目標放在LCP上。 在2.5秒時,FCP非常高(我們稍後會討論),但FCP和LCP之間的近3秒差距是需要改進的。
優化LCP
React Storefront的主要產品圖像(只是占位符-中間帶有“Product 1”的綠色框)被呈現為帶有指向https://via.placeholder.com的src URL的HTML img標記。該站點有一個像樣的TTFB (大約150毫秒),如果遷移到更快的CDN (如Layer0 CDN-as-JavaScript),則可以改進該功能。 然而,我們懷疑150毫秒是FCP與LCP之間將近3秒的差距。 為了消除這一差距,我們使用base64數據URL將圖像內聯,如下所示:
我們通過下載主映像,將其轉換為base64,並在Layer0的Serverless JavaScript雲上的伺服器端渲染過程中將其填充到src屬性中來實現這一點。 以下是產品API端點中的行:
const mainProductImage = result.product.media.full[0]
mainProductImage.src = await getBase64ForImage(mainProductImage.src)
以下是擷取影像並將影像轉換為底數64的程式碼:
import fetch from 'node-fetch'
export default async function getBase64ForImage(src) {
const res = await fetch(src)
const contentType = res.headers.get('content-type')
const buffer = await res.buffer()
return `data:${contentType};base64,${buffer.toString('base64')}`
return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}`
}
很簡單,很老派 以及對分數的影響?
通過將LCP從5.3秒降至2.8秒,我們在該頁面的Lighthouse v6.0得分中獲得了21分! 這種小小的更改會對Lighthouse v6.0的得分產生巨大影響,這讓人有點不安,但我們會接受這一點。 需要注意的是,各次跑步之間的所有指標都有所不同,但整體得分始終保持在80年代的低水平。 在上下文中,在v6.0上,性能最高的領先電子商務網站得分為87,根據PSI測量,它看起來像是從90年代中脫穎而出-看看www.rockauto.com
上面顯示的FCP和LCP之間的差距與我們在幾次執行中看到的差距一樣大。 大多數情況下,間隙在100ms到300ms範圍內。 有時,FCP和LCP是相同的。
優化TBT
接下來,我們嘗試改進TBT。 這是相當具有挑戰性的。 如前所述,要改進LCP,您需要縮小JavaScript包的大小或縮短水合時間。 對於大多數應用程式,根本不可能開始剝離依賴關係以縮小綑綁包。 使用React Storefront 7構建的應用程式已經受益於許多編譯時優化,這些優化可最小化由Next.js的webpack配置提供的綑綁包大小,以及針對材料UI的特定Babel優化。 那麼,我們在哪些方面可以改進? 水合時間。
懶惰的水分,贏了!
幸運的是,React Storefront社群在Lighthouse v6.0掉線之前已經開始支援懶散水合。 這當然使我們加快了我們的努力。
在您不知道的情況下,水分是指對伺服器上呈現的HTML元素進行控制的反應,以便它們能夠交互。 按鈕可點擊;轉盤可旋轉等 一個頁面包含的組件越多,水分所需的時間就越長。 復雜的組件(如主菜單和產品圖像傳送帶)需要更長時間。
滯後水合意味著延遲某些成分的水合,直到絕對必要,最重要的是,在初始頁面載入之後(以及計算TBT之後)。 懶惰的水分可能有風險。 您必須確保頁面元素已準備好響應用戶輸入,然後用戶才能嘗試與其交互。
由於Material UI依賴於CSS-in-JS,只有在組件水分後才將樣式動態添加到文檔中,因此在React Storefront上實施滯後水分非常困難。 我將保存詳細資訊,以便下次使用。 最後,我們構建了LazyHydrate組件,開發人員可以將其插入組件樹中的任何位置,以延遲水化,直到發生特定事件(例如,元素滾動到視口或用戶觸摸顯示器)。
以下是我們對顯示主要產品圖像的MediaCarousel進行緩慢水分的示例:
import LazyHydrate from 'react-storefront/LazyHydrate'
我們在應用中的幾個區域應用了惰性水分,最顯著的是:
- 滑入式菜單:當用戶點擊漢堡按鈕時,我們會對其進行水分。
- 摺頁下方的所有控件:包括尺寸,顏色和數量選擇器以及產品信息選項卡。
- 主圖像轉盤:此組件和主菜單可能是功能最多的組件,因此水分最昂貴。
以下是應用了懶散水分的Lighthouse v6.0分數:
慵懶的水分將三丁基錫化合物降低了將近40%,並將TTI (與v6.0中的分數相比,其重量為15%)削減了700毫秒。 這在Lighthouse v6.0的整體得分中獲得了6分。
您會注意到FCP上揚了一點,但LCP上揚了一點。 這些細微的變化基本上都是在執行PageSpeed Insights時所聽到的“噪音內”。 每次跑步時,所有分數都會略有波動。
優化FCP
根據上述分數,我們認為FCP和/或LCP可以進一步改進。 我們知道腳本可以阻止渲染,因此我們了解了Next.js如何將腳本導入到文檔中:
在此處使用異步可能不是最佳選擇。 如果在渲染過程中下載了腳本,則它可以在評估腳本時暫停渲染,這會增加FCP和LCP的時間。 使用defer而不是async可確保腳本僅在繪製文檔後才進行評估。
遺憾的是,Next.js不允許您更改腳本的導入方式,因此我們需要按以下方式擴展NextScript組件:
import React from 'react'
import { NextScript as OriginalNextScript } from 'next/document'
/**
* A replacement for NextScript from `next/document` that gives you greater control over how script elements are rendered.
* This should be used in the body of `pages/_document.js` in place of `NextScript`.
*/
export default class NextScript extends OriginalNextScript {
static propTypes = {
/**
* Set to `defer` to use `defer` instead of `async` when rendering script elements.
*/
mode: PropTypes.oneOf(['async', 'defer']),
}
static defaultProps = {
mode: 'async',
}
getScripts() {
return super.getScripts().map(script => {
return React.cloneElement(script, {
key: script.props.src,
defer: this.props.mode === 'defer' ? true : undefined,
async: this.props.mode === 'async' ? true : undefined,
})
})
}
}
然後我們將以下內容添加到pages/_document.js:
令人高興的是,這確實提高了LCP和整體得分:
它還會在許多運行中輕微地撞擊FCP,但這可能在“噪音”範圍內。 然而,使用延遲與異步時,整體分數持續高出2-3分。
總結
當Lighthouse v6.0於2020年5月底發布時,包括React Storefront應用程式在內的許多站點的性能分數都大幅下降。 在優化之前,React Storefront入門級應用程式的PDP性能處於60年代的低水平。 通過這些優化,我們將其融入到現在已形成的低90年代的空氣中。 此時,我們認為進一步提高得分的唯一方法是消除依賴性,這可能意味著在開發人員生產力的基礎上犧牲應用程式性能。
這是另一次討論。 讓我向您介紹一下我們嘗試過的但不起作用的一些事情:
前置
Preact使綑綁包的大小縮小了20到30 %,但Lighthouse v6.0的分數在所有指標(甚至TTI)中始終都較差。 我們不知道為什麼,但我們知道這不是Lighthouse v6.0的新功能或獨有功能。 使用Lighthouse v5.7時速度也會變慢。 我們將繼續定期檢查,希望有一天這種情況會得到解決。
分塊
next.js最近引入了更精細的瀏攬器資產塊。 當在Next.js 9.1中首次引入此功能時,我們注意到增加的較小塊實際上使TTI變得更糟。 它可能會使應用程式在新版本發布後更快地返回用戶,因為它可以更好地利用瀏攬器暫存,但Lighthouse並不關心這一點。 因此,React Storefront將瀏攬器塊的數量限制在一段時間內:
const webpack = require('webpack')
const withReactStorefront = require('react-storefront/plugins/withReactStorefront')
module.exports = withReactStorefront({
target: 'serverless',
webpack: config => {
config.plugins.push(
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
})
)
return config
},
})
網頁字體
大多數站點使用自定義Web字體。 預設情況下,React Storefront使用Roboto (儘管可以更改或刪除)。 自定義網頁字體會降低性能,簡單而簡單。 移除網頁字型,您將獲得約1秒的FCP。
與分析一樣,利益相關者似乎願意以績效為代價,使用特定字體。 React Storefront提供與網站相同來源的網頁字型,以避免您從第三方CDN (例如Google字型)載入字型時所需的TLS交涉時間。 具體而言,我們使用了NPM的字體機器人包。 與webpack的cs-loader結合使用時,使用typeface-roboto會導致字體通過單獨的CSS文件導入,瀏攬器需要下載並解析該文件。 我們認為將CSS內嵌到文件中可能有助於提高效能,但卻沒有。
在性能方面,您始終需要進行測量。 理論上應該起作用的優化在實踐中可能並不存在。
提高燈塔分數
我們的客戶在Lighthouse v6.0上排名前500位的電子商務網站中排名第95位。