von 3 MB unpurged CSS zum schlanken Production-Build
Ein frisches Hyvä-Theme liefert im Entwicklungsmodus ein CSS-Bundle von mehreren Megabyte aus, weil Tailwind CSS im Watch-Modus bewusst nicht purged. Der CSS-Purge-Schritt im Production-Build scannt jede phtml- und Alpine.js-Datei nach tatsächlich verwendeten Klassennamen und reduziert das Stylesheet dadurch auf wenige Kilobyte, sofern dynamische Klassennamen korrekt behandelt und die Deploy-Sequenz sauber ausgeführt wird.
Inhaltsverzeichnis
- 1. Warum unpurged Hyvä-CSS auf 3 MB anwächst
- 2. Wie der Tailwind-Scanner Klassennamen erkennt
- 3. Dynamische Klassennamen in phtml und Alpine: Warum Safelisting nötig ist
- 4. npm run watch vs. Production-Build im Docker-Setup
- 5. Minifizierung mit Lightning CSS und cssnano
- 6. Die Deploy-Sequenz: Damit der CSS-Purge den Storefront erreicht
- 7. Dateigröße verifizieren: du -h, ls -la und CI-Budget
- 8. Third-Party-Module und Content-Globs
- 9. CSS-Purge im Vergleich: Unpurged Dev-Build vs. Purged Production-Build
- 10. Zusammenfassung
- 11. FAQ
1. Warum unpurged Hyvä-CSS auf 3 MB anwächst
Ein frisch aufgesetztes Hyvä-Theme liefert im Entwicklungsmodus ein CSS-Bundle, das schnell 2 bis 3 MB groß wird, unminifiziert und ohne CSS-Purge. Der Grund liegt im Aufbau von Tailwind CSS selbst: Das Framework generiert theoretisch tausende Utility-Klassen, denn jede Kombination aus Farbe, Abstand, Breakpoint und Zustand wie hover, focus oder dark erzeugt eine eigene Regel. Ohne CSS-Purge landen all diese Regeln im ausgelieferten Stylesheet, auch wenn im tatsächlichen phtml-Markup nur ein Bruchteil davon verwendet wird.
In der Entwicklung mit npm run watch ist das gewollt: Jede neu geschriebene Klasse soll sofort verfügbar sein, ohne dass ein Rebuild-Zyklus wartet. Der Preis dafür ist Größe, nicht Fehlerhaftigkeit. Kritisch wird es erst, wenn dieses unpurged Bundle versehentlich in Production landet: Ladezeiten steigen, Lighthouse-Scores fallen, und der Storefront lädt CSS-Regeln für Klassen, die im gesamten Projekt kein einziges Mal vorkommen. Der CSS-Purge-Schritt im Production-Build ist deshalb keine nachträgliche Optimierung, sondern ein struktureller Teil der Build-Pipeline, ohne den Hyvä-Themes ihr zentrales Performance-Versprechen nicht einhalten können.
2. Wie der Tailwind-Scanner Klassennamen erkennt
Tailwind CSS enthält keinen klassischen Compiler, der HTML im herkömmlichen Sinn parst. Stattdessen arbeitet der Scanner, der den CSS-Purge antreibt, als reiner Text-Tokenizer: Er liest jede Datei in den konfigurierten content-Pfaden, typischerweise *.phtml, *.js und *.html im Theme-Verzeichnis, und sucht nach Zeichenketten, die wie eine gültige CSS-Klasse aussehen. Es gibt keine Ausführung von PHP oder JavaScript, keine Template-Auswertung, nur eine regelbasierte Suche nach Wortfolgen ohne Leerzeichen, die Tailwinds Syntax entsprechen.
Das bedeutet: Der CSS-Purge-Mechanismus erkennt eine Klasse nur, wenn sie als vollständige, zusammenhängende Zeichenkette im Quelltext auftaucht. class="bg-blue-600 text-white p-4" wird erkannt, jede der drei Klassen bleibt im finalen Bundle erhalten. Der Scanner kennt keine Semantik, er kennt nur String-Matching. Genau diese Eigenschaft macht den Purge-Prozess vorhersehbar und schnell, sie ist aber gleichzeitig die Wurzel des häufigsten Hyvä-Fehlers: Sobald Klassennamen zur Laufzeit aus Teilstrings zusammengesetzt werden, sieht der Scanner nur die Fragmente, nicht das Ergebnis.
3. Dynamische Klassennamen in phtml und Alpine: Warum Safelisting nötig ist
In Hyvä-phtml-Templates und Alpine.js-Komponenten ist es verlockend, Klassennamen dynamisch zusammenzusetzen, etwa class="text-<?= $color ?>-500" oder ein Alpine-Binding wie :class="'bg-' + status + '-100'". Für den CSS-Purge-Scanner existiert an dieser Stelle keine vollständige Klasse, sondern nur die Fragmente text- und -500 beziehungsweise bg- und -100. Da keiner dieser Teilstrings einer gültigen Tailwind-Utility entspricht, wird die zugehörige CSS-Regel beim CSS-Purge entfernt, und im Browser fehlt schlicht das Styling, oft erst bei einem bestimmten Statuswert sichtbar und dadurch schwer zu reproduzieren.
Die Lösung ist Safelisting: Klassen, die der Scanner nicht statisch erkennen kann, werden explizit von der Entfernung ausgenommen, entweder als feste Liste einzelner Klassennamen oder als Regex-Pattern, das eine ganze Gruppe abdeckt. Der bessere Weg ist aber, dynamische Konstruktionen so umzuschreiben, dass vollständige Klassennamen im Quelltext stehen, etwa über eine Lookup-Map mit festen Werten pro Status. Das reduziert den Safelist-Umfang und hält den CSS-Purge im Hyvä-Theme präzise, statt ihn mit pauschalen Ausnahmen auszuhöhlen.
// tailwind.config.js (excerpt) - safelist for dynamically built class names
export default {
content: [
'./app/design/frontend/Mironsoft/default/**/*.phtml',
'./app/design/frontend/Mironsoft/default/**/*.js',
],
safelist: [
// Alpine :class="'bg-' + status + '-100'" cannot be detected by the scanner
'bg-green-100', 'bg-red-100', 'bg-amber-100',
'text-green-700', 'text-red-700', 'text-amber-700',
// Regex pattern covers a whole group of dynamic color classes at once
{ pattern: /^(bg|text)-(red|green|amber)-(100|500|700)$/ },
],
}
4. npm run watch vs. Production-Build im Docker-Setup
Im Mark-Shust-Docker-Setup existieren zwei grundsätzlich unterschiedliche Befehle für den Tailwind-Build. npm run watch, aufgerufen im Theme-Verzeichnis, startet den Entwicklungsmodus: unminifiziert, ohne CSS-Purge, mit Datei-Watcher für sofortiges Rebuilding bei jeder Änderung. Das erzeugte Stylesheet ist bewusst vollständig und kann durchaus 2 bis 3 MB umfassen, denn Geschwindigkeit beim Speichern zählt hier mehr als Dateigröße.
Für den produktiven Einsatz kommt stattdessen bin/npm --prefix app/design/frontend/[Vendor]/[Theme]/web/tailwind run build zum Einsatz, ausgeführt über den Docker-Wrapper. Dieser Befehl aktiviert den vollständigen Production-Modus von Tailwind CSS v4: CSS-Purge über den Scanner, anschließende Minifizierung und das Zusammenführen aller Layer in eine einzige, kompakte Datei. Der Unterschied zwischen beiden Modi ist nicht graduell, sondern kategorisch, denn watch ist niemals für den produktiven Einsatz gedacht. Wer versehentlich das Watch-Ergebnis deployed, liefert ungepurged, unminifiziertes CSS an echte Besucher aus, oft ohne es sofort zu bemerken, da die Seite optisch weiterhin korrekt aussieht.
# Development: unpurged, unminified, instant rebuild on save
bin/npm --prefix app/design/frontend/Mironsoft/default/web/tailwind run watch
# Check dev bundle size (typically 2-3 MB, unpurged)
du -h src/pub/static/frontend/Mironsoft/default/de_DE/Magento_Theme/css/styles.css
# 2.9M src/pub/static/frontend/Mironsoft/default/de_DE/Magento_Theme/css/styles.css
# Production: runs the Tailwind scanner, purges unused classes, minifies via Lightning CSS
bin/npm --prefix app/design/frontend/Mironsoft/default/web/tailwind run build
# Check production bundle size after the CSS purge step
du -h app/design/frontend/Mironsoft/default/web/tailwind/css/styles.css
# 28K app/design/frontend/Mironsoft/default/web/tailwind/css/styles.css
5. Minifizierung mit Lightning CSS und cssnano
Nach dem CSS-Purge folgt in Tailwind CSS v4 automatisch ein Minifizierungsschritt, der auf Lightning CSS aufbaut, einem in Rust geschriebenen Parser und Transformer, der deutlich schneller arbeitet als die früher gebräuchliche Kombination aus PostCSS und cssnano. Lightning CSS entfernt Whitespace und Kommentare, kürzt Farbwerte, fasst identische Regeln zusammen und optimiert Selektoren, ohne die Kaskade zu verändern. Für Projekte, die zusätzlich eine klassische PostCSS-Pipeline einsetzen, bleibt cssnano weiterhin eine gängige Alternative mit vergleichbarem Ergebnis.
Der Effekt der Minifizierung ist deutlich kleiner als der des CSS-Purge selbst, aber keineswegs vernachlässigbar. Während der CSS-Purge-Schritt die Datei typischerweise um über 95 Prozent verkleinert, sorgt die anschließende Minifizierung nochmals für 10 bis 20 Prozent Einsparung obendrauf, vor allem durch Whitespace-Entfernung und kürzere Selektorenschreibweise. Beide Schritte zusammen ergeben aus einem 3 MB großen Entwicklungs-Bundle typischerweise eine Production-Datei zwischen 15 und 40 KB, abhängig davon, wie viele individuelle Utility-Kombinationen ein Theme tatsächlich nutzt.
{
"scripts": {
"watch": "tailwindcss -i ./css/source.css -o ./css/styles.css --watch",
"build": "NODE_ENV=production tailwindcss -i ./css/source.css -o ./css/styles.css --minify"
},
"devDependencies": {
"@tailwindcss/cli": "^4.0.0",
"lightningcss": "^1.25.0"
}
}
6. Die Deploy-Sequenz: Damit der CSS-Purge den Storefront erreicht
Ein häufig übersehener Punkt: Ein erfolgreicher CSS-Purge im Build-Schritt bedeutet noch nicht, dass die purgede Datei auch tatsächlich im Storefront ankommt. Magento cached kompilierte Assets in var/view_preprocessed und veröffentlicht sie nach pub/static/frontend. Bleiben dort alte, unpurged Versionen liegen, liefert der Server weiterhin die große Datei aus, unabhängig davon, wie klein das frisch gebaute CSS tatsächlich ist.
Die korrekte Deploy-Sequenz beginnt deshalb immer mit dem Löschen der Caches: rm -rf var/view_preprocessed/* pub/static/frontend/* im Magento-Root, gefolgt von bin/magento setup:static-content:deploy de_DE -t [Vendor]/[Theme] -f, das die frisch gepurgte und minifizierte CSS-Datei neu generiert und im static-content-Verzeichnis ablegt. Erst der abschließende Befehl bin/magento cache:flush stellt sicher, dass Magentos interner Full-Page-Cache und Konfigurations-Cache keine Referenzen mehr auf die alte Datei-Hash-Version enthalten. Wird einer dieser drei Schritte übersprungen, insbesondere das Löschen von var/view_preprocessed, sieht man im Frontend gelegentlich weiterhin veraltetes, ungepurged CSS, obwohl der CSS-Purge-Prozess selbst korrekt gelaufen ist.
#!/usr/bin/env bash
# deploy-css.sh - deploy purged production CSS to the storefront
set -euo pipefail
cd /var/www/html
# 1. Remove stale compiled assets (must run first)
rm -rf var/view_preprocessed/*
rm -rf pub/static/frontend/*
# 2. Rebuild static content from the purged, minified source
bin/magento setup:static-content:deploy de_DE -t Mironsoft/default -f
# 3. Flush Magento caches so hashed asset URLs update
bin/magento cache:flush
echo "[OK] Purged CSS deployed to storefront"
7. Dateigröße verifizieren: du -h, ls -la und CI-Budget
Ob der CSS-Purge tatsächlich gegriffen hat, lässt sich in Sekunden prüfen. Der Befehl du -h pub/static/frontend/[Vendor]/[Theme]/de_DE/Magento_Theme/css/styles.css zeigt die tatsächliche Dateigröße auf der Festplatte, ls -la liefert zusätzlich Zeitstempel und exakte Byte-Angabe. Ein Wert im niedrigen zweistelligen KB-Bereich signalisiert einen erfolgreichen CSS-Purge-Durchlauf, ein Wert im MB-Bereich deutet fast immer darauf hin, dass entweder der falsche Build-Befehl lief oder ein alter Cache-Stand ausgeliefert wird.
Für Teams, die Regressionen frühzeitig erkennen wollen, lohnt sich ein Size-Budget in der CI-Pipeline: Ein einfaches Shell-Skript prüft nach jedem Build die Dateigröße gegen einen festen Schwellenwert, etwa 100 KB, und lässt den Build fehlschlagen, sobald dieser überschritten wird. Das verhindert, dass ein versehentlich zu breiter Content-Glob oder eine übermäßig großzügige Safelist unbemerkt in die Production-Umgebung gelangt. Ein CSS-Purge-Budget in der CI-Pipeline ist damit ein einfacher, aber wirksamer Schutzmechanismus gegen schleichenden Bundle-Größen-Zuwachs.
#!/usr/bin/env bash
# ci-css-budget.sh - fail the build if purged CSS exceeds the size budget
set -euo pipefail
CSS_FILE="app/design/frontend/Mironsoft/default/web/tailwind/css/styles.css"
BUDGET_KB=100
actual_kb=$(du -k "$CSS_FILE" | cut -f1)
if (( actual_kb > BUDGET_KB )); then
echo "[ERROR] CSS bundle is ${actual_kb} KB, budget is ${BUDGET_KB} KB" >&2
exit 1
fi
echo "[OK] CSS bundle is ${actual_kb} KB (within ${BUDGET_KB} KB budget)"
8. Third-Party-Module und Content-Globs
Der Tailwind-Scanner durchsucht ausschließlich die Pfade, die im content-Array konfiguriert sind, typischerweise app/design/frontend/[Vendor]/[Theme]/**/*.phtml sowie die entsprechenden JavaScript-Dateien des eigenen Themes. Klassennamen, die in Third-Party-Modulen außerhalb dieser Globs liegen, etwa in einem separat installierten Modul oder einer Erweiterung mit eigenen phtml-Dateien in einem anderen Vendor-Verzeichnis, werden vom CSS-Purge-Scanner schlicht nicht gesehen und deshalb aus dem finalen Bundle entfernt.
Das Ergebnis: Ein Modul liefert augenscheinlich korrektes HTML mit gültigen Tailwind-Klassen aus, aber ohne zugehöriges CSS, weil die Regel beim CSS-Purge entfernt wurde. Die Lösung liegt darin, den content-Pfad im Tailwind-Setup so zu erweitern, dass er alle relevanten Vendor-Verzeichnisse einschließt, nicht nur das aktive Theme. Bei häufig wechselnden oder vielen Drittanbieter-Modulen ist ein zentrales Pattern wie app/design/frontend/**/*.phtml sinnvoller als einzelne, hart codierte Pfadangaben pro Modul.
9. CSS-Purge im Vergleich: Unpurged Dev-Build vs. Purged Production-Build
Der Unterschied zwischen unpurged Entwicklungs-CSS und purgtem Production-Bundle zeigt sich am deutlichsten im direkten Vergleich der Kennzahlen. Er betrifft nicht nur die reine Dateigröße, sondern wirkt sich unmittelbar auf Ladezeit, wahrgenommene Performance und Lighthouse-Bewertung aus, gerade auf mobilen Verbindungen mit begrenzter Bandbreite.
| Kennzahl | Unpurged Dev-Build | Purged Production-Build | Effekt |
|---|---|---|---|
| CSS-Dateigröße | ~2,9 MB | ~28 KB | Rund Faktor 100 kleiner |
| HTTP-Antwort (gzip) | ~480 KB | ~9 KB | Deutlich weniger Übertragungsvolumen |
| Ladezeit (simuliertes 3G) | ~1,8 s | ~0,2 s | Spürbar schnellere Interaktivität |
| Lighthouse Performance | ~62 | ~96 | Bessere Core-Web-Vitals-Bewertung |
| Genutzte Utility-Klassen | alle generierten (~34.000) | nur verwendete (~450) | Nur tatsächlich referenzierte Klassen |
Die Tabelle macht deutlich, warum ein sauberer CSS-Purge kein kosmetisches Detail ist, sondern direkt in Geschäftskennzahlen wie Conversion-Rate und Absprungrate einzahlt. Ein Shop, der versehentlich unpurged CSS ausliefert, verliert spürbar an Time-to-Interactive, ganz ohne dass im Code ein funktionaler Fehler vorläge.
10. Zusammenfassung
Der CSS-Purge im Hyvä-Theme löst ein strukturelles Problem von Tailwind CSS: Der Entwicklungsmodus generiert bewusst ein vollständiges, mehrere Megabyte großes Stylesheet, weil Geschwindigkeit beim Speichern wichtiger ist als Dateigröße. Der Tailwind-Scanner arbeitet als reiner Text-Tokenizer über phtml- und JavaScript-Dateien und behält nur Klassen, die als vollständige Zeichenkette im Quelltext stehen. Dynamische Konstruktionen wie class="text-<?= $color ?>-500" oder Alpine-Bindings mit String-Konkatenation entgehen dieser Erkennung und brauchen Safelisting oder eine Umstellung auf vollständige Klassennamen.
Der Weg von 3 MB auf wenige KB führt über den richtigen Build-Befehl, die Minifizierung mit Lightning CSS und eine vollständige Deploy-Sequenz aus Cache-Löschung, Static-Content-Deploy und Cache-Flush. Ein Größenbudget in der CI-Pipeline macht Regressionen sofort sichtbar, bevor ein zu breiter Content-Glob oder eine ausufernde Safelist den Effekt des CSS-Purge wieder zunichtemacht.
CSS-Purge im Hyvä-Theme: Das Wichtigste auf einen Blick
Scanner-Mechanik
Der Tailwind-Scanner sucht als Text-Tokenizer nach vollständigen Klassennamen in phtml und JS. Keine Semantik, nur String-Matching.
Safelist statt Bruch
Dynamisch zusammengesetzte Klassennamen brauchen Safelisting, sonst entfernt der CSS-Purge die zugehörige Regel unbemerkt.
watch vs. build
npm run watch ist unpurged und unminifiziert. Nur bin/npm ... run build liefert das produktionsreife Bundle.
Deploy-Sequenz
rm -rf var/view_preprocessed und pub/static/frontend, dann setup:static-content:deploy und cache:flush, in dieser Reihenfolge.
11. FAQ: CSS-Purge im Hyvä-Theme
1Was ist CSS-Purge im Hyvä-Theme?
2Warum wird das Hyvä-CSS im Dev-Modus so groß?
3Wie erkennt der Tailwind-Scanner Klassennamen?
4Warum funktioniert class="text-<?= $color ?>-500" nicht?
5Was ist Safelisting und wann braucht man es?
6watch vs. Production-Build?
7Welche Rolle spielt Lightning CSS?
8Warum var/view_preprocessed löschen?
9Wie prüfe ich den CSS-Purge-Erfolg?
10Warum fehlt CSS für ein Third-Party-Modul?
Mironsoft
Tailwind CSS, Hyvä-Themes und Performance-Optimierung für Magento 2
CSS-Bundle im Storefront zu groß?
Wir analysieren euer Hyvä-Theme, richten den CSS-Purge-Prozess korrekt ein, ersetzen fragile dynamische Klassennamen durch sichere Muster und sorgen dafür, dass die Deploy-Sequenz das purgede CSS auch tatsächlich in den Storefront bringt.
CSS-Purge-Audit
Bundle-Größe, Safelist-Umfang und Content-Globs auf Schwachstellen prüfen
Build-Pipeline
Production-Build, Minifizierung und Deploy-Sequenz sauber einrichten
CI-Integration
CSS-Größenbudget in die Pipeline integrieren und Regressionen verhindern