Ungenutzte Tailwind Klassen auditieren: Content Scanning und Bundle Hygiene
AI generated
</>
tw
Tailwind CSS · Bundle Hygiene · Performance
Ungenutzte Tailwind Klassen auditieren
Content Scanning und Bundle Hygiene

Tailwinds Content Scanning verspricht, dass nur tatsächlich genutzte Klassen im finalen CSS landen. In der Praxis sammeln sich trotzdem ungenutzte Tailwind Klassen an, durch entfernte Komponenten, überzogene Safelists und dynamisch zusammengesetzte Klassennamen, die das Scanning gar nicht erst als sicher einstufen kann.

17 Min. Lesezeit Tailwind CSS v4 · Content Scanning · CSS Coverage Bundle-Analyse · CI-Integration

1. Warum ungenutzte Tailwind Klassen überhaupt entstehen

Auf den ersten Blick scheint das Problem ungenutzte Tailwind Klassen paradox, schließlich generiert Tailwind ausschließlich CSS für Klassen, die im Content-Scanning tatsächlich im Quellcode gefunden werden. Trotzdem sammelt sich in gewachsenen Projekten über Zeit erheblicher CSS-Bloat an, und die Ursache liegt fast nie im Scanning-Mechanismus selbst, sondern in der Diskrepanz zwischen dem, was einmal im Code stand, und dem, was heute tatsächlich gerendert wird.

Der häufigste Auslöser ist gelöschter oder umgebauter Code, der Klassenreferenzen zurücklässt, die technisch noch im Quellcode existieren, aber praktisch nie mehr erreicht werden, etwa in auskommentiertem Code, ungenutzten Feature-Flag-Zweigen oder alten Test-Fixtures. Der zweite häufige Auslöser ist eine übervorsichtige Safelist, die Klassen dauerhaft ins Bundle zwingt, obwohl der dynamische Anwendungsfall, für den sie ursprünglich gedacht war, längst nicht mehr existiert. Beide Fälle führen zu ungenutzten Tailwind Klassen, die niemand aktiv hinzugefügt hat, sondern die als Nebenprodukt normaler Code-Evolution entstehen.

2. Wie Content Scanning funktioniert und wo es an Grenzen stößt

Tailwinds Content Scanning durchsucht alle konfigurierten Quelldateien nach Textmustern, die wie gültige Utility-Klassen aussehen, und generiert für jeden Treffer die entsprechende CSS-Regel. Dieser Mechanismus ist bewusst simpel gehalten: Es findet keine echte Code-Analyse statt, sondern ein reiner Textabgleich, der auch in Kommentaren, in String-Literalen oder in nicht mehr erreichbarem Code fündig wird. Genau diese Einfachheit ist der Grund, warum ungenutzte Tailwind Klassen überhaupt ins finale Bundle gelangen können, obwohl das Scanning technisch korrekt funktioniert.

Ein zweiter Grenzfall des Content Scannings betrifft Dateiformate, die nicht in der content-Konfiguration erfasst sind. Wird eine Komponente aus einem Monorepo-Package eingebunden, dessen Pfad nicht explizit im Scanning-Glob steht, generiert Tailwind für dessen Klassen gar kein CSS, was zu fehlenden statt zu ungenutzten Tailwind Klassen führt, aber denselben Debugging-Aufwand erzeugt, weil beide Symptome sich im Browser identisch als fehlendes Styling zeigen.


# Quick sanity check: does the content config actually cover every source path?
grep -A 5 "content:" tailwind.config.js

# Cross-check against the real project structure
find . -type d -name "node_modules" -prune -o \
  -type f \( -name "*.html" -o -name "*.phtml" -o -name "*.twig" \) -print \
  | wc -l

3. Werkzeuge zur Analyse: CSS Coverage in den DevTools

Der direkteste Weg, ungenutzte Tailwind Klassen im tatsächlichen Betrieb zu identifizieren, ist der Coverage-Tab der Chrome DevTools. Nach dem Laden einer Seite zeigt dieser Tab exakt an, welcher Prozentsatz des geladenen CSS beim Rendern der aktuellen Seite tatsächlich angewendet wurde, aufgeschlüsselt bis auf einzelne Byte-Bereiche innerhalb der Datei. Für eine Single-Page-Ansicht liefert das eine sofortige, verlässliche Momentaufnahme, hat aber eine wichtige Einschränkung: Es misst nur, was auf der aktuell geladenen Seite gebraucht wird, nicht was im gesamten Projekt über alle Seiten hinweg relevant ist.

Für eine projektweite Aussage über ungenutzte Tailwind Klassen braucht es deshalb eine Automatisierung, die Coverage-Daten über mehrere repräsentative Seitentypen hinweg aggregiert, etwa Startseite, Produktdetailseite, Checkout und Kontoseite. Nur die Schnittmenge aus ungenutzten Regeln über alle getesteten Seiten hinweg lässt sich mit hoher Sicherheit als tatsächlich toten Code einstufen, während eine Regel, die nur auf einer einzigen, nicht mitgetesteten Seite verwendet wird, fälschlich als ungenutzt erscheinen kann.


// coverage-audit.js — aggregate CSS coverage across multiple page types
import puppeteer from "puppeteer";

const pagesToCheck = [
  "https://staging.example.com/",
  "https://staging.example.com/catalog/product/view/id/42",
  "https://staging.example.com/checkout/cart",
  "https://staging.example.com/customer/account",
];

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.coverage.startCSSCoverage();

for (const url of pagesToCheck) {
  await page.goto(url, { waitUntil: "networkidle0" });
}

const coverage = await page.coverage.stopCSSCoverage();
let usedBytes = 0;
let totalBytes = 0;

for (const entry of coverage) {
  totalBytes += entry.text.length;
  for (const range of entry.ranges) {
    usedBytes += range.end - range.start;
  }
}

console.log(`CSS usage across ${pagesToCheck.length} pages: ${((usedBytes / totalBytes) * 100).toFixed(1)}%`);
await browser.close();

4. Custom Skripte: generiertes CSS gegen echte Nutzung abgleichen

Neben Browser-Coverage-Daten lohnt sich ein statischer Abgleich auf Build-Ebene: Ein Skript extrahiert alle Klassennamen aus dem generierten Tailwind-CSS und prüft für jede einzelne, ob sie als exakter String noch irgendwo im aktuellen Quellcode vorkommt. Dieser Ansatz findet ungenutzte Tailwind Klassen, die zwar syntaktisch gültig generiert wurden, aber zu keiner tatsächlich existierenden Code-Referenz mehr gehören, etwa weil die Komponente, die sie einst nutzte, komplett gelöscht wurde, ohne dass die zugehörigen Safelist-Einträge mit entfernt wurden.

Der Vorteil gegenüber reiner Browser-Coverage ist, dass ein solches Skript den kompletten Quellcode abdeckt, nicht nur die Seiten, die in einem Testlauf tatsächlich besucht wurden. Der Nachteil: Es erkennt keine Klassen, die zwar im Code stehen, aber durch bedingte Logik zur Laufzeit nie erreicht werden, etwa in einem permanent deaktivierten Feature-Flag-Zweig. Beide Methoden ergänzen sich deshalb, statt sich gegenseitig zu ersetzen, wenn es darum geht, ungenutzte Tailwind Klassen vollständig zu erfassen.


// find-unused-classes.js — cross-check generated CSS against source usage
import fs from "node:fs";
import { glob } from "glob";

const cssContent = fs.readFileSync("pub/static/css/tailwind.min.css", "utf8");
const generatedClasses = new Set(
  [...cssContent.matchAll(/\.([a-zA-Z0-9_-]+)\s*[{,:]/g)].map((m) => m[1])
);

const sourceFiles = await glob("src/templates/**/*.{html,phtml,twig}");
const usedClasses = new Set();

for (const file of sourceFiles) {
  const content = fs.readFileSync(file, "utf8");
  for (const cls of generatedClasses) {
    if (content.includes(cls)) usedClasses.add(cls);
  }
}

const unused = [...generatedClasses].filter((c) => !usedClasses.has(c));
console.log(`${unused.length} of ${generatedClasses.size} generated classes appear unused.`);
console.log(unused.slice(0, 30).join("\n"));

5. Dynamisch generierte Klassennamen als blinde Flecke

Ein besonders hartnäckiger Sonderfall bei ungenutzten Tailwind Klassen betrifft Klassennamen, die zur Laufzeit aus Variablen zusammengesetzt werden, etwa text-${statusColor}-600. Content Scanning erkennt solche Muster grundsätzlich nicht, weil kein vollständiger Klassenname als String im Quellcode existiert. Projekte lösen das meist über eine Safelist, die alle möglichen Kombinationen vorab auflistet, was aber automatisch dazu führt, dass Kombinationen für Werte, die im aktuellen Anwendungsfall nie auftreten, dauerhaft als ungenutzte Tailwind Klassen im Bundle verbleiben.

Die nachhaltigere Lösung ist, dynamische Klassenkomposition möglichst zu vermeiden und stattdessen eine explizite Lookup-Tabelle im Code zu pflegen, die jeden möglichen Zustand auf eine vollständige, für das Scanning sichtbare Klasse abbildet. Das erhöht zwar die Zeilenzahl im Code geringfügig, macht aber jede tatsächlich benötigte Klasse für Tailwinds Scanning sichtbar und macht eine Safelist mit Dutzenden spekulativen Kombinationen überflüssig, von denen die meisten ohnehin nie gebraucht werden.


/* PROBLEM: dynamic class composition is invisible to content scanning */
/* text-${statusColor}-600 requires an oversized safelist to work at all */

/* BETTER: explicit lookup table, every class is a real, scannable string */
/*
  const statusColorClass = {
    success: "text-green-600",
    warning: "text-amber-600",
    error: "text-red-600",
    pending: "text-slate-600",
  }[status];
*/

6. Safelist-Hygiene: veraltete Einträge identifizieren

Die Safelist-Konfiguration ist eine der häufigsten Quellen für ungenutzte Tailwind Klassen, weil sie per Definition Klassen unabhängig von der tatsächlichen Nutzung ins Bundle zwingt. Über Jahre wachsen Safelists häufig, ohne dass jemals ein Eintrag wieder entfernt wird, weil das Entfernen eines Eintrags im Gegensatz zum Hinzufügen ein Risiko birgt: Niemand will versehentlich eine Klasse entfernen, die doch noch irgendwo gebraucht wird. Diese Vorsicht führt aber genau zu dem CSS-Bloat, den man eigentlich vermeiden möchte.

Eine regelmäßige, geplante Safelist-Revision löst dieses Dilemma: Jeder Safelist-Eintrag wird testweise für einen begrenzten Zeitraum entfernt, während ein automatisierter visueller Regressionstest über alle wichtigen Seiten läuft. Bricht dabei nichts sichtbar, war der Eintrag mit hoher Wahrscheinlichkeit eine ungenutzte Tailwind Klasse und kann dauerhaft entfernt werden. Dieser Prozess sollte in festen Abständen wiederholt werden, weil sich neue veraltete Safelist-Einträge mit jeder Produktänderung erneut ansammeln.

7. Komponentenbibliotheken als Quelle von CSS-Bloat

Projekte, die eine zentrale Komponentenbibliothek über mehrere Produkte oder Frontends hinweg teilen, produzieren häufig ungenutzte Tailwind Klassen, weil die Bibliothek für den größten gemeinsamen Nenner aller Konsumenten gebaut wird, aber jedes einzelne Frontend nur einen Teil der Varianten tatsächlich verwendet. Eine Button-Komponente mit acht Farbvarianten und vier Größen erzeugt potenziell 32 Kombinationen an generierten Klassen, obwohl ein konkretes Projekt vielleicht nur vier davon tatsächlich einsetzt.

Der zuverlässigste Weg, diese Art von CSS-Bloat zu reduzieren, ist eine strikte Trennung zwischen der Komponentenbibliothek als Quelle für Utility-Kombinationen und dem tatsächlichen Content-Scanning-Ziel jedes einzelnen konsumierenden Projekts. Statt die komplette Bibliothek pauschal in die content-Konfiguration jedes Frontends aufzunehmen, sollte nur der tatsächlich im jeweiligen Projekt verwendete Ausschnitt gescannt werden, was ungenutzte Tailwind Klassen aus nie eingesetzten Varianten von vornherein verhindert.

8. CI-Integration: automatisierte Bundle-Größen-Checks

Manuelle Audits von ungenutzten Tailwind Klassen finden in der Praxis selten regelmäßig statt, weil sie Zeit kosten und leicht in den Hintergrund rücken. Ein automatisierter CI-Check, der die CSS-Bundle-Größe bei jedem Pull Request gegen den letzten bekannten Wert vergleicht, macht ungewöhnliches Wachstum sofort sichtbar, ohne dass ein manuelles Audit dafür nötig wäre. Überschreitet die Größenänderung einen definierten Schwellenwert, wird der Build als Warnung markiert, statt das Wachstum unbemerkt zu akkumulieren.

Ergänzend dazu kann ein periodischer, wöchentlicher CI-Job die vollständige Coverage-Analyse über alle repräsentativen Seiten laufen lassen und das Ergebnis als Kommentar in einem dedizierten Tracking-Issue dokumentieren. So bleibt der Zustand der ungenutzten Tailwind Klassen im Projekt jederzeit nachvollziehbar, ohne dass jemand manuell an das Thema denken muss, bevor das Bundle bereits spürbar aufgebläht ist.


#!/usr/bin/env bash
# ci-bundle-size-check.sh — fail the build on unexpected CSS growth
set -euo pipefail

CURRENT_SIZE=$(stat -f%z pub/static/css/tailwind.min.css 2>/dev/null \
  || stat -c%s pub/static/css/tailwind.min.css)
BASELINE_SIZE=$(cat .ci/css-baseline-bytes.txt)
THRESHOLD_PERCENT=5

DIFF=$(( (CURRENT_SIZE - BASELINE_SIZE) * 100 / BASELINE_SIZE ))

echo "Baseline: ${BASELINE_SIZE} bytes, current: ${CURRENT_SIZE} bytes (${DIFF}% change)"

if [[ $DIFF -gt $THRESHOLD_PERCENT ]]; then
  echo "[WARNING] CSS bundle grew by more than ${THRESHOLD_PERCENT}%. Review for unused classes."
  exit 1
fi

9. Analyse-Methoden im Vergleich

Die folgende Tabelle vergleicht die vorgestellten Methoden zur Aufspürung von ungenutzten Tailwind Klassen nach Genauigkeit, Automatisierungsgrad und typischem Einsatzfall.

Methode Genauigkeit Automatisierbar Typischer Einsatz
DevTools Coverage Hoch, pro Seite Teilweise (Puppeteer) Stichprobenartige Tiefenanalyse
Statischer Klassenabgleich Mittel Ja, voll Regelmäßiges Projekt-Audit
Safelist-Revision Hoch, manuell verifiziert Gering Periodische Bereinigung
CI-Bundle-Größen-Check Niedrig, nur Trend Ja, voll Kontinuierliches Monitoring

Keine einzelne Methode deckt ungenutzte Tailwind Klassen vollständig ab. Die robusteste Strategie kombiniert einen automatisierten CI-Check für die laufende Überwachung mit periodischen, tieferen Audits über Coverage-Analyse und Safelist-Revision, statt sich auf ein einziges Werkzeug zu verlassen.

Mironsoft

Web Performance, CSS-Bundle-Analyse und Tailwind-Optimierung für Magento und Hyvä

Ein Tailwind-Bundle voller toter Klassen?

Wir analysieren euer CSS-Bundle mit Coverage-Tools und Custom-Skripten, bereinigen veraltete Safelist-Einträge und richten CI-Checks ein, die zukünftiges CSS-Bloat dauerhaft verhindern.

Bundle-Audit

Coverage-Analyse über alle wichtigen Seitentypen mit konkreten Zahlen

Safelist-Bereinigung

Sichere Revision veralteter Einträge mit visuellem Regressionstest

CI-Integration

Automatisierte Bundle-Größen-Checks in eurer Pipeline

10. Zusammenfassung

Ungenutzte Tailwind Klassen entstehen fast nie durch einen Fehler im Content Scanning selbst, sondern durch die natürliche Diskrepanz zwischen gelöschtem Code, dynamisch generierten Klassennamen und überzogenen Safelist-Konfigurationen, die über die Zeit ohne Bereinigung wachsen. DevTools Coverage liefert präzise Momentaufnahmen einzelner Seiten, statische Klassenabgleiche decken das gesamte Projekt ab, und regelmäßige Safelist-Revisionen entfernen den größten Einzelposten an totem CSS.

Der nachhaltigste Schutz vor ungenutzten Tailwind Klassen ist eine Kombination aus periodischen, tiefen Audits und einem automatisierten CI-Check, der jedes ungewöhnliche Bundle-Wachstum sofort meldet. So bleibt die Bereinigung ein kontinuierlicher, wenig aufwendiger Prozess statt einer seltenen, dafür umso schmerzhafteren Großaktion.

Ungenutzte Tailwind Klassen auditieren: Das Wichtigste auf einen Blick

Hauptursachen

Gelöschter Code, überzogene Safelists und dynamische Klassenkomposition, die Content Scanning nicht erkennen kann.

DevTools Coverage

Präzise pro Seite, muss aber über mehrere Seitentypen aggregiert werden für eine projektweite Aussage.

Safelist-Hygiene

Regelmäßige, testweise Entfernung einzelner Einträge mit visuellem Regressionstest deckt tote Einträge auf.

CI-Absicherung

Automatisierter Bundle-Größen-Check meldet ungewöhnliches Wachstum sofort bei jedem Pull Request.

11. FAQ: Ungenutzte Tailwind Klassen auditieren

1Wie entstehen ungenutzte Klassen trotz Scanning?
Textabgleich findet Klassen auch in gelöschtem Code und überzogenen Safelists.
2Schnellster Weg für eine Seite?
Coverage-Tab der Chrome DevTools zeigt sofort die tatsächliche CSS-Nutzung an.
3Reicht eine Seite für die Analyse?
Nein, mehrere Seitentypen müssen aggregiert werden, sonst falsche Positive.
4Warum sind Safelists eine häufige Quelle?
Sie erzwingen Klassen unabhängig von Nutzung und werden aus Vorsicht selten bereinigt.
5Wie finde ich veraltete Safelist-Einträge?
Testweise entfernen und visuellen Regressionstest über alle wichtigen Seiten laufen lassen.
6Was tun bei dynamischen Klassennamen?
Explizite Lookup-Tabelle statt Template-String macht Safelist überflüssig.
7Führen Komponentenbibliotheken zu Bloat?
Ja, gemeinsamer Nenner aller Konsumenten erzeugt Varianten, die einzelne Projekte nicht nutzen.
8Geht die Entfernung komplett automatisch?
Skripte identifizieren Kandidaten, endgültiges Entfernen braucht visuellen Regressionstest.
9Wie überwache ich das kontinuierlich?
CI-Check vergleicht Bundle-Größe bei jedem Pull Request gegen den letzten Wert.
10Fehlend vs. ungenutzt, was ist der Unterschied?
Fehlend: content-Pfad deckt den Ordner nicht ab. Ungenutzt: generiert, aber nirgends mehr gebraucht.