Erscheinungsbild
0015 — Performance-Budgets als Gate im Build
Status: angenommen · Milestone: M8
Kontext
Der Starter ist die Grundlage von Kundenwebsites, die niemand mehr messen wird, nachdem sie online sind. Was an Gewicht hineinkommt, kommt in kleinen Schritten: eine Bibliothek für einen Slider, eine dritte Schriftfamilie, ein zweites Hero-Bild mit priority. Jeder Schritt ist einzeln vertretbar, und die Summe fällt erst im Lighthouse-Bericht auf — also dann, wenn jemand danach fragt.
Eine Zusage über Ladezeiten kann dieses Repository nicht geben: Die hängt am Netz, am Gerät und am Hosting. Was es geben kann, ist eine Zusage über das, was es selbst in der Hand hat — wie viel eine Seite mitbringt.
Entscheidung
Budgets werden auf der gebauten Ausgabe gemessen und lassen den Build scheitern. Zuständig ist die Integration kicktemp:performance-budget; die Grenzen stehen an einer Stelle, in BUDGETS in src/lib/deployment/budget.ts.
Geprüft wird je Seite:
| Größe | Grenze | Gemessen |
|---|---|---|
| JavaScript | 40 KiB | gzip |
| CSS | 30 KiB | gzip |
| HTML | 30 KiB | gzip |
| Vorgeladene Schriften | 200 KiB | unkomprimiert |
Bilder mit priority | 1 | Anzahl |
| Bilder ohne Maße | 0 | Anzahl |
Dazu einmal für die gesamte Ausgabe: höchstens 6 verschiedene Inline-Skripte.
Gemessen wird mit gzip, nicht mit Brotli — weil die erzeugte .htaccess gzip ausliefert (ADR 0012). Schriften werden unkomprimiert gemessen, weil woff2 bereits komprimiert ist.
Ab 80 Prozent eines Budgets wird gewarnt, ohne den Build aufzuhalten.
Befunde werden nach Ursache zusammengefasst, nicht nach Seite.
Die Integration läuft für jedes Zielsystem, auch für GitHub Pages.
Begründung
Warum ein Abbruch und keine Warnung. Eine Warnung im Build-Protokoll ist eine Notiz. Sie wird beim ersten Mal gelesen, beim dritten Mal überblättert, und beim zehnten Mal weiß niemand mehr, ob sie schon immer dastand. Der Starter hat dieselbe Entscheidung schon einmal so getroffen: checkFontBudget() bricht ab, wenn eine dritte Schriftfamilie dazukommt. Ein Gate ist unbequem genau in dem Moment, in dem die Entscheidung fällt — und das ist der einzige Moment, in dem sie noch billig ist.
Warum die Grenzen nicht am heutigen Stand festgezogen sind. Der Starter braucht derzeit 3 KiB JavaScript und 14 KiB CSS je Seite. Ein Budget bei 5 und 16 KiB wäre eine schöne Zahl und würde beim ersten Kundenprojekt zweimal angehoben — danach beachtet es niemand mehr. Die Grenzen sind so gesetzt, dass ein normales Projekt hineinpasst und eine versehentlich mitgelieferte Bibliothek nicht. 40 KiB JavaScript reichen für die Consent-Schicht und eine Vue-Island; wer mehr braucht, trifft eine Entscheidung und schreibt sie in den Commit.
Warum die Schriften mitgezählt werden. Sie sind der größte Posten auf dem kritischen Pfad und der unauffälligste: Sie stehen in src/lib/fonts.ts, nicht in einer Komponente, und ihre Größe sieht man nirgends. Der Starter liegt hier bei 175 KiB — sechs vorgeladene Dateien aus zwei Familien, drei Schnitten und zwei Subsets. Das ist innerhalb des Budgets und nah an seiner Grenze, und die Warnung sagt das bei jedem Build. Sie ist der ehrlichere Zustand als ein weiteres Budget, das genau darüber liegt.
Warum höchstens ein priority-Bild je Seite. Mehr als eines hebt die Priorisierung auf: Zwei Bilder konkurrieren dann um dieselbe Leitung, und keines kommt früher an. Die Regel stand bisher nur in CLAUDE.md; hier wird sie geprüft.
Warum Maße an jedem Bild. Ohne width und height kennt der Browser das Seitenverhältnis nicht, und der Inhalt darunter springt beim Laden. Das ist der einzige Anteil an CLS, der sich an einer statischen Datei überhaupt feststellen lässt — alles Weitere braucht einen Browser und steht in den End-to-End-Tests.
Warum die Zahl der Inline-Skripte ein Budget ist. Jedes braucht einen eigenen Hash in der Content Security Policy (ADR 0013). Wächst diese Zahl mit der Zahl der Seiten, wächst der Header mit ihr. Die Grenze ist deshalb kein Performance-Wert, sondern die Bedingung, unter der der Hash-Ansatz tragbar bleibt.
Warum nach Ursache zusammengefasst wird. Eine Website hat auf jeder Seite dieselben Schriften und dasselbe CSS-Bündel. Sechs gleichlautende Zeilen zu melden liest sich wie sechs Probleme, ist aber eines — und in einem Kundenprojekt mit achtzig Seiten wären es achtzig Zeilen für denselben Satz. Die eine Zeile, die woanders herkommt, ginge darin unter.
Verworfene Alternativen
Lighthouse CI im Workflow. Es misst mehr, darunter das, worauf es am Ende ankommt. Es misst aber auf einem Runner, dessen Auslastung niemand kennt, und liefert deshalb Werte, die zwischen zwei Läufen um zweistellige Punktzahlen schwanken. Ein Gate, das ohne Änderung am Code mal rot und mal grün ist, wird abgeschaltet. Es bleibt eine sinnvolle Ergänzung — als Bericht, nicht als Gate, und nicht in diesem Milestone.
Budgets nur dokumentieren. Genau das war der Zustand vor diesem Milestone: In CLAUDE.md stand die Regel zu priority, und geprüft war sie nicht.
Auf das unkomprimierte Bündel prüfen. Die Zahl wäre größer und einfacher zu ermitteln, aber sie kommt auf keiner Leitung so an. Ein Budget gegen eine Größe zu prüfen, die niemand überträgt, erzeugt Diskussionen über die falsche Zahl.
Messen mit Brotli. Brotli ist typisch 10 bis 15 Prozent kleiner und wäre die freundlichere Zahl. Ausgeliefert wird aber gzip — jede Seite sähe damit kleiner aus, als sie ankommt.
Konsequenzen
- Jeder Build fährt die Prüfung, auch die beiden Fixture-Builds. Ein Budget, das nur in einer Konfiguration greift, prüft die andere nicht.
- Die Warnung zu den Schriften erscheint derzeit bei jedem Build. Sie ist richtig: Der Starter liegt bei 175 von 200 KiB. Wer sie loswerden will, reduziert Schnitte oder Subsets in
src/lib/fonts.ts— nicht das Budget. - Wer ein Budget anhebt, tut das in
BUDGETSund begründet es im Commit. Der Abbruchtext sagt es. - Bilder aus
Pic.astrobringen ihre Maße mit (ADR 0005); ein<img>, das von Hand geschrieben wird, fällt in dieser Prüfung auf.