Notizbuch · Notiz 001 · Web-Performance

Wo sich auf einer statischen Website die letzten 18 Punkte versteckten

Bilder komprimiert, JavaScript in drei Dateien, kein Framework. Lighthouse zeigte trotzdem 82. Die fehlenden Punkte steckten an drei Stellen, die das Tool zwar benannte, aber nicht erklärte.

Serhat Belen

Gründer, BLN Global · · 7 Min. Lesezeit

Kurze Antwort

Bilder und JavaScript waren bereits gut. Die restlichen Punkte steckten an drei Stellen. Erstens: Die Prüfung, die unnötige Arbeit vermeiden sollte, las selbst das Layout aus und zwang den Browser zum Rechnen. Zweitens: Das Stylesheet und externe Schriftarten verzögerten die erste Darstellung um 600 ms. Wir haben das CSS in die Seite eingebettet und die Schriften auf unseren Server gelegt. Drittens: Von 24 Kontrastwarnungen der Barrierefreiheitsprüfung waren 6 Fehlalarme. Der vollständige Scan fand danach 13 Seiten, die in der Stichprobe fehlten.

Auf dieser Website läuft kein Framework. Python-Skripte erzeugen statisches HTML für die Seiten. Die Styles stehen in einer CSS-Datei, JavaScript in drei kleinen Dateien. Die Bilder liegen als WebP in drei Größen vor. Für die Performance hatten wir die offensichtlichen Punkte also schon erledigt.

Der Mobile-Score blieb bei 82.

Nach den offensichtlichen Fehlern fehlten die restlichen Punkte nicht wegen eines großen Problems. Sie steckten an drei verschiedenen Stellen. Alle zu finden hieß weniger, die Warnung des Tools zu lesen, als die Warnung was du meinst du musstest es verstehen.

Platz eins: die Prüfung, die Arbeit überspringen sollte

Lighthouse warnt mit „erzwungener Reflow“, wenn JavaScript den Browser zwingt, das Seitenlayout zu früh zu berechnen. Der Browser sammelt Stiländerungen und wendet sie auf einmal an. Liest das Skript dazwischen einen Messwert aus, muss der Browser die gesammelten Änderungen sofort verarbeiten.

Die Warnung zeigte auf die Quellzeile, doch dort war keine sichtbare Messung. Nach drei Durchgängen war die Ursache klar.

  • Im ersten Durchlauf getComputedStyle Aufrufe entfernt. Der Score blieb gleich.
  • In der zweiten Runde zeigte sich der eigentliche Täter: ein Skript, das unnötige Arbeit überspringen sollte if (window.scrollY > 0) Prüfung. Die Prüfung selbst liest das Layout aus. Die Zeile, die Arbeit vermeiden sollte, löste genau diese Arbeit aus.
  • Im dritten Durchgang flog die Funktion komplett raus. CSS definierte den Ausgangszustand bereits. JavaScript musste ihn nicht noch einmal setzen.

Ein Muster bleibt noch void element.offsetWidth war. Ein gängiger, akzeptierter Trick: das Layout bewusst erzwingen, damit eine Transition neu startet. Stattdessen zwei requestAnimationFrame kam dazu. Erfüllt denselben Zweck, ohne das Layout zu erzwingen.

function yenidenBaslat(fn) { /* Läuft der Tab im Hintergrund, startet rAF nicht und die Demo bleibt leer. */ if (document.hidden) { fn(); return; } requestAnimationFrame(function () { requestAnimationFrame(fn); }); }
Den Übergang neu starten, ohne das Layout zu erzwingen. Für Hintergrund-Tabs braucht es einen Ausweg. Dort läuft rAF nicht.

Die Erkenntnis: Eine Performancewarnung sagt meist nicht „Du machst zu viel“, sondern „Du machst es zum falschen Zeitpunkt“.

Platz zwei: die 600 ms vor dem ersten Paint

Die zweite Warnung betraf Ressourcen, die den ersten Bildaufbau blockierten. Der Browser zeichnete nichts, bevor er das Stylesheet und danach die Schriften geladen und verarbeitet hatte. Die gemessene Verzögerung lag bei 600 Millisekunden.

Stylesheet steckt direkt in der Seite

Eine eigene CSS-Datei kommt beim zweiten Besuch aus dem Cache. Das hilft. Beim ersten Besuch kostet sie aber einen zusätzlichen Roundtrip, und genau diesen ersten Besuch erlebt jemand aus der Suche. Beim Deploy verkleinern wir CSS und betten es in die Seite ein. Wir haben gemessen: Nach Brotli war die eingebettete Variante kleiner als separate Datei plus zweiter Request.

Fonts liegen auf unserem Server

Das Setup von Google Fonts verband sich mit zwei getrennten Origins: für Styles mit fonts.googleapis.com, für Dateien fonts.gstatic.com. Jede neue Quelle braucht eine DNS-Auflösung, einen TLS-Handshake und eine neue Verbindung. Ohne diese Anfragen messen wir folgendes Ergebnis:

250 KB165 KB

Die Schriftdateien einer türkischen Seite. 3 Dateien aus einer Quelle statt 6 Dateien und 2 externen Quellen.

Wie wir gemessen haben Chrome rief das Google Fonts-CSS mit einer echten Kennung ab, unicode-range über die Felder haben wir die Subsets getrennt, die eine türkische Seite wirklich lädt (latin + latin-ext), und jede Dateigröße einzeln gemessen. Die andere Seite in diesem Repo assets/fonts echte Größe der Dateien.

Wie die Schriftdateien kleiner wurden und was die Erweiterung auf zehn Sprachen kostete, steht in einer eigenen Notiz: zehn Sprachen, drei Schriftdateien.

Dritte Stelle: hier liegt das Tool falsch

Die Barrierefreiheitsprüfung meldete 24 Kontrastprobleme. Hätten wir alle ungeprüft korrigiert, wäre die Hälfte des Designs kaputtgegangen. 6 Meldungen waren falsch.

Grund content-visibility:auto war. Diese Funktion verarbeitet Bereiche außerhalb des Bildschirms erst später und beschleunigt so das erste Rendering. Genau das wollten wir. Doch die berechnete Farbe des zurückgestellten Bereichs erreichte das Prüfwerkzeug nicht. Deshalb meldete es Weiß auf Weiß. Auf dem Bildschirm gab es dieses Problem nicht.

Wir haben die echten Verhältnisse einzeln im Browser gemessen. 18 Warnungen stimmten und wurden korrigiert. 6 lagen am blinden Fleck des Tools: content-visibility Wir haben es nur aus den zwei problematischen Abschnitten entfernt. In den übrigen blieb es stehen.

Die Warnung eines Audit-Tools beweist keinen Fehler. Sie ist eine Behauptung. Wir prüfen sie, dann beheben wir den Fehler. Diese Lektion tauchte auf dieser Seite noch zweimal auf. Einmal bei in der Notiz zur Sprachprüfung steht dort.

Dazu kommt unser eigener Fehler

Damit das Layout nicht springt, brauchen alle Bilder width und height Das Attribut kam dazu. Richtiger Schritt. Der Browser reserviert den Platz für das Bild, bevor er es lädt. Aber die Demo-Boxen brachen: Einige Bilder erschienen 58 Pixel breit und 1500 Pixel hoch.

Der Grund: im HTML width und height Attribute geben Darstellungshinweise, und die Werte im CSS height sie füllen den Wert aus. Im Design gibt es für diese Felder nur aspect-ratio war definiert, height war leer. Das Attribut landete dort und drückte die Rate. Korrektur in einer Zeile:

img{max-width:100%;height:auto;display:block}
Die Attribute bleiben stehen und verhindern weiter Layout-Verschiebungen. CSS setzt die Höhe zurück.

Und hier bringt der Scan wirklich etwas

Als die Startseite 100 hatte, wirkte die Sache erledigt. War sie nicht. Wir hatten nur eine Stichprobe gemessen. Der Website alle 150 Seiten liefen einzeln durch Lighthouse. Das Ergebnis: 137 Seiten erreichten in allen vier Kategorien 100, nicht 13 Seiten. Barrierefreiheit, Best Practices und SEO lagen auf jeder Seite bei 100. Nur die Performance wich ab.

Elf von dreizehn Seiten hatten dieselbe Sprache: Arabisch. Der niedrigste Wert: 83.

/ar/safecoast/ 83 /ar/laya/ 84 /ar/localmind/ 84 /ar/adforge/ 85 /ar/garanti/ 85 /ar/gtin/ 86 /ar/visionguard/ 86 /ar/ 89 /ar/growthos/ 97 /ar/dream/ 99 /ar/twin/ 99
Die arabischen Seiten aus dem vollständigen Crawl. Die beiden übrigen Abweichungen (/pl/modelmesh/ und /ru/terms.html, beide 98) schwanken von Messung zu Messung im normalen Bereich.

Lighthouse nannte den Grund direkt: Webfont geladen → ar.woff2. Die arabische Schrift kam zu spät, der Browser setzte die Seite neu. Die arabische Ersatzschrift maß ganz anders als Noto Sans Arabic, deshalb fiel der Sprung groß aus. Für die Korrektur trafen wir dieselbe Entscheidung wie schon bei Martian Mono: swap nicht optional.

0,2870

Layout-Verschiebungen (CLS) auf arabischen Seiten

Wie wir gemessen haben Nach der Änderung haben wir 15 arabische Seiten neu gemessen: Bei allen 15 lag CLS bei null. Die Performance-Scores stiegen von 83-89 auf 98-100, 9 der 15 Seiten erreichten exakt 100. Gemessen haben wir auf der Live-Site mit Lighthouse.

Die eigentliche Lektion betrifft nicht die Performance. Wenn du nur Stichproben prüfst und der Website 100 Punkte gibst, behauptest du etwas über Seiten, die du nicht gemessen hast. Fünfzehn Seiten blieben deshalb monatelang bei 83 Punkten. Wir hatten sie nie geprüft.

Grenze des Ergebnisses

Der Punktwert stammt aus einem Labortest. Er setzt ein festes Gerät und ein festes Netzmodell voraus. Das Telefon und die Verbindung echter Besucher können schlechter sein. Mehrere Messungen derselben Seite schwanken zwischen 98 und 100. 100 Punkte bedeuten nicht, dass die Seite für alle schnell ist. Sie bedeuten, dass wir die messbaren Hindernisse entfernt haben. Sobald genug echte Nutzerdaten vorliegen, zählt Chromes Felddatensatz.

Diese drei Korrekturen hatten eins gemeinsam: Keine davon bedeutete mehr Arbeit. Alle drei haben Arbeit entfernt, die zum falschen Zeitpunkt lief oder gar nicht nötig war.

Häufige Fragen

Was meint Lighthouse mit erzwungenem Reflow?

Dass JavaScript den Browser zwingt, gesammelte Stiländerungen zu früh zu berechnen. Der Browser bündelt Änderungen und wendet sie auf einmal an. Liest der Code dazwischen einen Messwert aus, muss er den Stapel sofort abarbeiten. offsetWidth, getBoundingClientRect, scrollY und getComputedStyle alle Reads lösen das aus.

Sollte CSS immer direkt in der Seite stehen?

Nein. Eine separate Datei kommt beim zweiten Besuch aus dem Cache. Das hilft. Einbetten macht den ersten Besuch schneller und wiederholte Besuche langsamer. Du musst nach Site entscheiden: Wenn viele Besucher aus der Suche kommen, gewinnt Einbetten. Wir haben es gemessen. Bei uns hat es gewonnen.

Solltest du content-visibility streichen?

Nein. Die Funktion verschiebt Bereiche außerhalb des Bildschirms und beschleunigt so den ersten Bildaufbau. Das Problem liegt beim Prüftool. Es kann die Farbe verschobener Bereiche nicht lesen. Entferne die Funktion nur dort, wo das Tool falsch meldet.

Bedeuten 100 Punkte wirklich, dass die Seite schnell ist?

Nein. Der Labortest setzt ein festes Gerät und ein festes Netz voraus. Wenn du dieselbe Seite mehrmals direkt hintereinander misst, schwankt der Wert zwischen 98 und 100. Was das in der Praxis bedeutet, zeigen die Felddaten von Chrome. Bis dort genug Daten stehen, vergeht Zeit.

Warum solltest du alle Seiten statt nur einer Stichprobe messen?

Bei uns tauchten 13 von 150 Seiten in der Stichprobe gar nicht auf, 11 davon in derselben Sprache. Die Startseite lag bei 100, die arabischen Seiten blieben bei 83. Über eine Seite, die du nicht misst, kannst du keine Aussage treffen.

Quellen

Ähnliche Arbeiten