Mein Thema: Optimierung der Webperformance eines VueJS Projekts durch Konfiguration der Vue-Templates und des Build-Prozesses.
Um zu zeigen, was ich gemacht habe, habe ich für heute meine Präsentation in 4 Teile unterteilt: 1. Vorstellung der Seite und was damit schlecht oder zumindest verbessungswürdig war 2. 11 Schritte, die ich vorgenommen habe, und was die (nicht) geändert haben 3. Ergebnisse, was rausgekommen ist 4. Ausblick, was noch fehlt
Die Seite, um die es geht, könnt ihr euch nebenbei oder im Anschluss unter leto.andreasnicklaus.de angucken und nutzen.
Die Seite, über die ich heute sprechen will, ist eine Marketing-Seite für Leto, eine Anwendung für Pyhsiotherapiepraxen. Zusätzlich ist eine Nutzerverwaltung und ein Admin-Dashboard für die Anwendung eigebunden. Die Seite ist also zumindest zum Teil dynamisch. Startseite: 6 animierte Elemente, 2 große PNGs Dev: VueJS, Bootstrap-Vue, Vuetify Deployment: AWS EC2-Instanz, Nginx Proxy Manager, Docker, Nginx
9 statische indexierte Seiten 3 dynamische & nicht-indexierte Seiten
Lass uns über die Probleme der Webseite reden
Ich habe 4 Indikatoren, wo der Effekt meiner Arbeit am besten merklich ist. 1. Lange TTI 2. Lang ladendes LCP 3. Lighthouse Performance von 33 4. Page Weight von fast 2,8 MB
Beim Page Weight, was ist am am Größten? 1. Bilder 78% 2. Javascript 17,4% 3. CSS 8,2% 4. Fonts 0,5% 5. HTML 0,01%
Welche Änderungen habe ich also vorgenommen? Liste an interessanten Änderungen stelle ich im Detail vor, deshalb überspringen wir das!
Erster Schritt: Im Build-Prozess mit dem PrerenderSpaPlugin, weil Vue mit einem HTML-Skelett arbeitet und es nach dem Laden des JS füllt. - nur die statischen Seiten - gut für den Index - zusätzlich werden alle Skripte mit den "defer" versehen, damit es dem Rendering des jetzt vorhandenen vollständigen HTML.
Der Effekt des Prerendering ist gleich erkennbar. - Die TTI verdreifacht sich auf fast 15 Sekunden, weil es einfach mehr zu Laden und animieren gibt - LCP wird schneller geladen, weil das HTML zum Laden der Bilder früher da ist - FCP genauso - aus diesen 2 Gründen: Performance 57
Im Zweiten Schritt habe ich versucht, Render-Blocking CSS automatisiert zu entfernen: Prerendering-Plugin erweitern durch replace-Funktionen Durch Vue-Plugins 2 Versionen, Stylesheets einzubinden 1. link-href-rel 2. link-rel-href Wird zu <link> mit rel=preload, durch js rel=stylesheet <noscript> <link rel=stylesheet> </noscript>
Was hat das gebracht? - TTI zum 4 Sekunden verbessert - FCP um 2,4 Sekunden, über 70% verbessert - LCP um 0,7 Sekunden verbessert, jetzt 50% des Originals - Lighthouse Performance von 57 auf 45 runter, k.A. wieso
[v05 Teil 1] v03 und v04 waren nicht zur Performanceoptimierung, deshalb v05: Chained Requests für Fonts Link zur Font in index.html anstelle vom Verweis im style-Block in App.vue
[v05 Teil 2] PNG zu AVIF-Format Außerdem in v05 habe ich die Einbindung von Bildern auf der Seite getauscht. Was vorher wie hier ein Image-Tag mit einem Srcset auf Webp-Bilder war,...
...ist jetzt ein Picture-Tag mit 2 Source-Tags: 1. für AVIF-Bilder 2. für Webp-Bilder mit den gleichen Größen.
Für die Conversion vom Originalbild in PNG habe ich das NPM-Package `sharp` benutzt und eine kleine Entdeckung für mich gemacht 1. Im Webp-Format ist das Bild in der gleichen Größe knapp ein Sechstel groß, aber die nächstkleinere Größe ist maginal kleiner 2. Im AVIF-Format ist das Bild in Originalgröße nur knapp halb so groß, aber die nächstkleinere Größe ist etwa ein Neuntel vom Orignalgroßen AVIF. Rechts das Bild, von dem die Daten kommen.
Was hat das gebracht? - TTI wieder auf einen semi-akzeptablen Wert von 6,1 Sekunden zurückgebracht - SI um 1,5 Sekunden verbessert auf neuen Bestwert von 2,5 Sekunden - Page Weigth um fast 60% reduziert -> Bilder - CLS wieder auf Orignalwert zurückgebracht -> Image Sizes
In Version 6 habe ich die Schrittweite zwischen den Bildgrößen geändert von 4 Bildern auf 6 Bildern mit gleicher Schrittweite
Genug von Bildern! In Version 7 und 8 habe ich den JS-Dateien angenommen. Als erstes ist mir aufgefallen, dass ein Großteil der JS-Module von Bootstrap-Icons eingenommen wurde, weil ich sie alle importiere. Also habe ich nur die genutzten Icons importiert und als Vue-Componenten registriert.
In v08 habe ich zusätzlich mit den JS-Chunks rumgespielt und mit den Built-In Optionen von Webpack die Größe der Dateien begrenzt. Motivation dafür war, dass die als erstes geladenen JS-Datei sehr groß war und vieles davon nicht auf der Startseite genutzt wird.
Was hats gebracht? - Anzahl und damit Granulatität an JS-Dateien verdoppelt - Durch Bootstrap Icons ca. 14% raw eingepart - Durch Bootstrap Icons ca. 21% gzipped eingepart
Animationen im Filmstrip als Visual Change vorhanden. Max 1100ms Verzügerung bis Start. Keine Änderung in sonstigen Metriken.
Formal gefehlt hats noch gefehlt, dass Bilder Lazy-Loaded geladen werden, deswegen habe ich das noch in Version 10 hinzugefügt und ... in Version 11 habe ich das noch für SVGs gemacht.
Was hat es gebracht? Aus irgendeinem Grund, hat das Lazy-Loading von Bildern sowohl die Lighthouse Performance als auch Speed Index und LCP verschlechtert, was sich durch das Lazy-Loading von SVGs wieder ausgebessert hat. Wenn also jemand eine Vermutung hat, woran das gelegen hat, bitte ich herzlich um Hilfe.
Ich habe die Metriken und Ergebnisse noch visualisiert, damit die Verbesserungen und Verschlechterungen besser sichtbar werden.
Zuerst zur Lighthouse Performance, die ich erst von 33 auf 57 durch Prerendering gehoben habe. Bis heute ist der Score bei 54.
Hier sehen wir, wie sehr der Score geschwankt hat
Die Time-To-Interactive habe ich runtergebracht von 4,6s auf 3,9s
Und hier können wir nochmal begutachten, dass im Laufe des Projekts durch Prerendering die TTI erst hochgegangen und dann durch die Umstellung auf AVIF-Bilder in Version 5 wieder runtergegangen ist.
Dann kommen wir jetzt zu den Kriterien, die sich tatsächlich verbessert haben. Den LCP habe ich halbieren können, aber...
wie eben angesprochen, gibt es einen Auschlag durch Lazy-Loading, den ich nicht erklären kann.
Zu guter Letzt das Page Weight habe ich reduziert bekommen von 2789 kB auf 612kB. Das ist passiert in den Versionen 5, 6 und 7 durch Reduzierung der Bildgröße und importierten Icons.
Das kann man an dem Graphen, dass die Versionen 5-7 tatsächlich effektiv waren und die Größe deutlich verbessert haben.
Jetzt, wo wir gesehen haben, was ich gemacht und erreicht habe, können wir nochmal einen kurzen Ausblick drauf werfen, was ich noch machen sollte. Nach dem Lighthouse Performance Report fehlt noch: 1. CSS Pruning, dass ungenutztes CSS entfernt wird. Das habe ich bisher nicht zum Laufen bekommen. 2. Da das Entfernen von Bootstrap-Icons so effektiv war, sollte Treeshaking auch noch automatisiert werden. 3. Thread Work ist laut Performance Report auch sehr groß. Ich habe bisher noch keinen Anhaltspunkt, wie ich anfange. 4. Das LCP-Bild dauert noch lange zu laden, deshalb sollte das Bild Preloaded werden, aber es ist größenabhängig, deshalb gehört da noch mehr Überlegung dazu. 5. Außerdem durch den Nginx Proxy Manager nicht sonderlich effektiv gehandelt: Caching. Mittels einem CDN, könnte das sehr verbessert werden. Nach dem Image Linter, den ich verwendet habe: 1. Von Hochkant-Bildern fehlen Bilder in großen Bildschirmgrößen -> Effektives Vergrößern von Bildern ohne höheren Datenaufwand