Layer0 ist ein wichtiger Beitrag zum Open Source eCommerce PWA Framework, React Storefront. Anfang dieses Jahres haben wir viele neue Funktionen und Optimierungen als beigesteuert React Storefront v7Zwei der bedeutendsten waren die Umstellung auf Next.js und die Entfernung mehrerer wichtiger Abhängigkeiten (z. B. MobX) zugunsten der neueren integrierten Funktionen von React zur Statusverwaltung, wie z. B. der useState Hook und die Kontext-API. Dies führte dazu, dass die Größe des Browser-Bundles etwa halbiert wurde.
Dies war zu dieser Zeit ein netter Gewinn und half, die Leistungswerte von Lighthouse (v5.7) für typische React Storefront-Apps von den 80er bis in die 90er-Jahre zu steigern, gemessen mit PageSpeed Insights (PSI). Im Kontext übertraf ein Ergebnis von 83+ 99 % der 500 besten E-Commerce-Websites auf Lighthouse v5.7. Wir wussten nicht, wie wichtig sich die Reduzierung des Pakets in den kommenden Monaten erweisen würde, als Lighthouse v6.0 wie eine Bombe fallen würde und die Leistungswerte aller auslöschte.
Sehen Sie, wie sich die Verteilung der Lighthouse-Scores, gemessen auf PSI, für die führenden eCommerce-Websites geändert hat, als v6.0 fiel:
Die neuen Kennzahlen in Lighthouse 6,0: TBT, LCP und CLS
Lighthouse v6.0 führt mehrere neue Metriken für die Wahrnehmungsgeschwindigkeit ein und formuliert neu, wie sich die Gesamtmetriken auf den Lighthouse Performance Score einer Seite auswirken.Leuchtturm 5,7 | Gewicht | Leuchtturm 6,0 | Gewicht |
---|---|---|---|
Erste Contentful Paint (FCP) | 20% | Erste Contentful Paint (FCP) | 15% |
Geschwindigkeitsindex (SI) | 27% | Geschwindigkeitsindex (SI) | 15% |
Erste aussagekräftige Farbe (Kineto-CTG) | 7% | Größte kontenvolle Farbe (LCP) | 25% |
Erster CPU-Leerlauf (FCI) | 13% | Gesamtblockierungszeit (TBT) | 15% |
Zeit bis interaktiv (TTI) | 33% | Zeit bis interaktiv (TTI) | 15% |
- | - | Kumulative Layoutverschiebung (CLS) | 5% |
Gesamtblockierungszeit (TBT)
Die gesamte Blockierungszeit ist eine neue Metrik, die in Lighthouse v6.0 enthalten ist und die Zeit misst, die das Parsen und Ausführen von JavaScript den Haupt-Thread während des Seitenladevorgangs blockiert. Diese Metrik behandelt moderne, JavaScript-intensive SPAs/PWAs sehr hart. Selbst mit der 50 % reduzierten Bundle-Größe von React Storefront 7 konnten wir einen Rückgang der Performance-Score von Lighthouse v6.0 um 20 bis 30 Punkte auf Produktseiten feststellen, was hauptsächlich auf die Einbeziehung von TBT als Kennzahl zurückzuführen ist, die 25 % der Gesamtleistung beeinflusst.
Wenn Sie ein isomorphes Framework verwenden, wie Next.js, das serverseitiges Rendering unterstützt, wird TBT hauptsächlich von der Paketgröße und der Hydratationszeit bestimmt. Einfach ausgedrückt: Die einzige Möglichkeit, TBT zu verbessern, besteht darin, Abhängigkeiten zu entfernen, Ihre Komponenten zu optimieren oder Ihre Website durch den Einsatz weniger Komponenten zu vereinfachen.
Größte Contentful Paint (LCP)
LCP ist eine neue Kennzahl, die 25 % über dem Gesamtwert von Lighthouse v6.0 liegt. LCP zielt darauf ab, zu quantifizieren, wie der Benutzer die anfängliche Seitenladeleistung wahrnimmt, indem er beobachtet, wie lange das größte contentful Element benötigt, um das Malen zu beenden. Bei den meisten Websites, insbesondere bei E-Commerce-Websites, ist das wichtigste contentful-Element das übergeordnete Bild. Bei Produktseiten in React Storefront Apps ist das größte contentful Element das Hauptproduktbild. Wenn Sie sich nicht sicher sind, welches Element auf Ihrer Website vorhanden ist, wird Ihnen PSI Folgendes mitteilen:
Kumulative Layoutverschiebung (CLS)
Die kumulative Layoutverschiebung misst, wie viel sich das Seitenlayout beim ersten Laden der Seite verschiebt. Layoutverschiebung wird am häufigsten durch Bilder verursacht, die dazu neigen, die Elemente um sie herum zu schieben, wenn sie die Größe ändern, um das Bild anzupassen, sobald die Daten aus dem Netzwerk heruntergeladen werden. Layoutverschiebung kann oft vollständig beseitigt werden, indem für jedes Bild Speicherplatz reserviert wird, bevor es geladen wird. Glücklicherweise hat React Storefront bereits diese Funktion übernommen, sodass die Starter-App React Storefront einen perfekten CLS-Wert von 0 vorab bietet. Es sollte darauf hingewiesen werden, dass andere häufige Schuldige von schlechten CLS Banner und Popups sind, die nach dem ersten Anmalen der Seite erscheinen. Benutzer hassen sie und jetzt auch Lighthouse.Wie wir React Storefront für Lighthouse v6.0 optimiert haben
Als wir die Produktseite der React Storefront Starter-App auf Lighthouse v6.0 mit PageSpeed Insights zum ersten Mal getestet haben, erzielte sie in den niedrigen 60er Jahren eine Punktzahl:Um die Punktzahl zu verbessern, haben wir zunächst LCP ins Visier genommen. Nach 2,5 Sekunden war die FCP besorgniserregend hoch (wir kommen später dazu), aber die fast 3 Sekunden lange Lücke zwischen FCP und LCP fiel als etwas aus, das verbessert werden musste.
Optimierung von LCP
Das Hauptproduktbild von React Storefront (lediglich ein Platzhalter – das grüne Feld mit „Product 1“ in der Mitte) wurde als HTML-img-Tag mit einer src-URL gerendert, die auf https://via.placeholder.com verweist. Diese Site hat eine anständige TTFB (ca. 150 ms), die durch die Umstellung auf ein schnelleres CDN wie Layer0 CDN-as-JavaScript verbessert werden könnte. Dennoch bezweifelten wir, dass 150 ms für die fast 3-Sekunden-Lücke zwischen FCP und LCP verantwortlich sind. Um die Lücke zu beseitigen, haben wir das Bild mit einer base64-Daten-URL wie dieser eingebettet:
Dazu wurde das Hauptbild heruntergeladen, in base64 konvertiert und während des serverseitigen Renderings in der serverlosen JavaScript-Cloud von Layer0 im Attribut src eingefügt. Hier ist die Linie des Produkt-API-Endpunkts:
const mainProductImage = result.product.media.full[0]
mainProductImage.src = await getBase64ForImage(mainProductImage.src)
Und hier ist der Code, der das Bild abruft und in Basis 64 konvertiert:
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')}`
}
Ziemlich einfache und alte Schule. Und die Auswirkungen auf die Punktzahl?
Indem wir den LCP von 5,3 auf 2,8 zurücksetzten, haben wir 21 Punkte im Lighthouse v6.0-Ergebnis der Seite gewonnen! Es ist etwas beunruhigend, wie eine solche kleine Veränderung den Wert von Lighthouse v6.0 dramatisch beeinflussen kann, aber wir nehmen es. Es ist zu beachten, dass alle Kennzahlen zwischen den Läufen etwas variieren, aber die Gesamtpunktzahl lag konsistent in den niedrigen 80er Jahren Für den Kontext: Die leistungsstärkste führende E-Commerce-Website auf v6.0 erreicht 87, gemessen an PSI, und sieht aus, als wäre sie direkt aus den 90er Jahren. Schauen Sie mal, www.rockauto.com
Die oben gezeigte Lücke zwischen FCP und LCP war etwa so groß wie wir sie in mehreren Runs gesehen haben. Meistens lag die Lücke im Bereich von 100 ms bis 300 ms. Gelegentlich waren FCP und LCP gleich.
TBT wird optimiert
Als Nächstes haben wir versucht, TBT zu verbessern. Das war ziemlich schwierig. Wie bereits erwähnt, müssen Sie zur Verbesserung der LCP entweder die Größe Ihres JavaScript-Bundles reduzieren oder die Hydratationszeit verbessern. Bei den meisten Apps ist es einfach nicht möglich, Abhängigkeiten zu entfernen, um das Paket zu verkleinern. Apps, die mit React Storefront 7 erstellt wurden, profitieren bereits von vielen Optimierungen für die Kompilierungszeit, die die Paketgröße minimieren, die durch die Webpack-Konfiguration von Next.js bereitgestellt wird, sowie von spezifischen Optimierungen für die Material-UI. Wo könnten wir uns also verbessern? Hydratationszeit.
Faule Hydration für den Sieg!
Glücklicherweise hatte die React Storefront-Community bereits begonnen, Lazy Hydration zu unterstützen, bevor Lighthouse v6.0 herunterging. Das hat uns sicherlich dazu gebracht, unsere Bemühungen zu beschleunigen.
Falls Sie sich nicht bewusst sind, bezieht sich Hydration darauf, dass die Reaktion die Kontrolle über HTML-Elemente übernimmt, die auf dem Server gerendert werden, sodass sie interaktiv werden können. Schaltflächen können angeklickt werden, Karussells können gezogen werden usw. Je mehr Komponenten eine Seite enthält, desto länger dauert die Hydration. Komplexe Komponenten wie das Hauptmenü und das Karussell des Produktbildes dauern noch länger.
Bei der Lazy Hydratation wird die Hydratation bestimmter Komponenten verzögert, bis sie unbedingt notwendig ist und vor allem nach dem ersten Seitenladen (und nach der Berechnung des TBT). Faule Hydration kann riskant sein. Sie müssen sicherstellen, dass Seitenelemente bereit sind, auf Benutzereingaben zu reagieren, bevor der Benutzer versucht, mit ihnen zu interagieren.
Die Implementierung von Lazy Hydration auf React Storefront erwies sich als recht schwierig, da Material UI auf CSS-in-JS angewiesen war, das dem Dokument erst nach der Hydratisierung der Komponenten dynamisch Stile hinzufügt. Ich werde die Details für ein anderes Mal speichern. Am Ende haben wir eine LazyHydrate-Komponente erstellt, die Entwickler an einer beliebigen Stelle in der Komponentenstruktur einfügen können, um die Hydration zu verzögern, bis ein bestimmtes Ereignis eintritt, z. B. wenn das Element in das Viewport gescrollt wird oder der Benutzer den Bildschirm berührt.
Hier ist ein Beispiel, wie wir das MediaCarousel mit Flüssigkeit versorgen, das die wichtigsten Produktbilder anzeigt:
import LazyHydrate from 'react-storefront/LazyHydrate'
Wir haben eine Lazy Hydration auf mehrere Anwendungsbereiche angewendet, vor allem:
- Das Slide-in-Menü: Wir versorgen dies, wenn der Benutzer auf die Hamburger-Taste tippt.
- Alle Steuerelemente unter dem Falz umfassen die Auswahl von Größe, Farbe und Menge sowie die Registerkarten mit Produktinformationen.
- Das Hauptbildkarussell: Dieses und das Hauptmenü sind wahrscheinlich die Komponenten mit der meisten Funktionalität und daher die teuersten zu hydratisierenden Komponenten.
Hier ist der Lighthouse v6.0-Score mit Anwendung von Lazy Hydration:
Lazy Hydration senkte TBT um fast 40 % und kürzte TTI (das ein Gewicht von 15 % gegenüber den Werten in v6.0 aufweist) um 700 ms. Dies führte zu einem 6-Punkte-Gewinn im Gesamtwert von Lighthouse v6.0.
Sie werden bemerken, dass FCP ein bisschen gestiegen ist, aber LCP ist abgesunken. Diese kleinen Änderungen sind im Wesentlichen „innerhalb des Geräuschs“, das beim Ausführen von PageSpeed Insights auftritt. Alle Punktzahlen schwanken zwischen den Läufen leicht.
Optimierung von FCP
Basierend auf der obigen Bewertung waren wir der Meinung, dass FCP und/oder LCP weiter verbessert werden könnten. Wir wissen, dass Skripte das Rendering blockieren können. Deshalb haben wir uns angesehen, wie Next.js Skripte in das Dokument importiert:
Die Verwendung von async ist hier möglicherweise nicht die beste Wahl. Wenn das Skript während des Renderns heruntergeladen wird, kann es das Rendering anhalten, während das Skript ausgewertet wird. Dadurch werden sowohl die FCP- als auch die LCP-Zeiten erhöht. Durch die Verwendung von DEFER anstelle von Async wird sichergestellt, dass Skripte erst nach dem Malen des Dokuments ausgewertet werden.
Leider erlaubt Next.js Ihnen nicht, die Art und Weise zu ändern, wie Skripte importiert werden. Daher mussten wir die NextScript-Komponente wie folgt erweitern:
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,
})
})
}
}
Anschließend haben wir Pages/_document.js Folgendes hinzugefügt:
Zu unserer Freude hat dies den LCP und die Gesamtergebnisse verbessert:
Auch bei vielen Läufen hat er die FCP leicht übertroffen, aber dies kann innerhalb des „Geräuschs“ liegen. Dennoch war die Gesamtpunktzahl bei Verwendung von DEFER vs. Async konstant um 2-3 Punkte höher.
Zusammenfassend
Als Lighthouse v6.0 Ende Mai 2020 veröffentlicht wurde, gingen die Leistungsbewertungen für viele Websites, einschließlich React Storefront-Apps, zurück. Vor den Optimierungen war die PDP-Leistung der React Storefront Starter-App in den niedrigen 60er Jahren verunglückt Mit diesen Optimierungen haben wir es in die jetzt rarisierte Luft der tiefen 90er-Jahre geschafft An diesem Punkt glauben wir, dass die einzige Möglichkeit zur weiteren Verbesserung der Punktzahl darin besteht, Abhängigkeiten zu beseitigen, was bedeuten kann, dass die Entwicklerproduktivität gegen die Anwendungsleistung abgewogen wird.
Das ist eine Diskussion für ein anderes Mal. Lassen Sie mich Ihnen einige Dinge überlassen, die wir ausprobiert haben, die nicht funktionierten:
Preact
Preact macht die Bündelgröße um 20 bis 30 % kleiner, aber die Scores von Lighthouse v6.0 waren bei allen Metriken, sogar bei TTI, durchweg schlechter. Wir haben keine Ahnung, warum, aber wir wissen, dass dies nicht neu oder exklusiv für Lighthouse v6.0 ist. Es war auch langsamer mit Lighthouse v5.7. Wir checken regelmäßig ein und hoffen, dass das eines Tages behoben wird.
Chunking
Next.js hat kürzlich eine feinkörnigere Chunking von Browser-Assets eingeführt. Als dies erstmals in Next.js 9,1 eingeführt wurde, stellten wir fest, dass die zusätzlichen, kleineren Blöcke TTI tatsächlich verschlechterten. Wahrscheinlich wird die App für wiederkehrende Benutzer nach der Veröffentlichung einer neuen Version schneller, da sie den Browser-Cache besser nutzen kann, aber Lighthouse interessiert nichts davon. React Storefront hat die Anzahl der Browserblöcke auf einen begrenzt:
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-Schriftarten
Die meisten Websites verwenden eine benutzerdefinierte Webschriftart. Standardmäßig verwendet React Storefront Roboto (dies kann jedoch geändert oder entfernt werden). Benutzerdefinierte Web-Schriftarten bringen Leistung in den Griff – einfach und einfach. Wenn Sie die Webschriftart entfernen, erhalten Sie etwa 1 Sekunde FCP.
Wie bei Analysen scheinen die Stakeholder bereit zu sein, die Leistung gegen eine bestimmte Schriftart abzuwägen. React Storefront stellt die Webschriftart vom gleichen Ursprung wie die Website bereit, um die TLS-Verhandlungszeit zu eliminieren, die Ihnen entstehen würde, wenn Sie die Schriftart von einem Drittanbieter-CDN wie Google Fonts geladen haben. Insbesondere verwenden wir das Typeface-roboto-Paket von NPM. In Kombination mit dem css-Loader von Webpack führt die Verwendung von typeface-roboto dazu, dass die Schriftart über eine separate CSS-Datei importiert wird, die der Browser herunterladen und parsen muss. Wir dachten, dass das Einfügen von CSS in das Dokument die Leistung verbessern könnte, aber das war nicht der Fall
Wenn es um Leistung geht, müssen Sie immer messen. Optimierungen, die theoretisch funktionieren sollten, sind möglicherweise nicht in der Praxis.
Erhöhen Sie Ihre Punktzahl für Lighthouse
Unsere Kunden liegen auf Lighthouse v6.0 im 95. Perzentil der 500 besten E-Commerce-Websites.