Design wechseln

Tailwind Performance-Optimierung: JIT, content-Konfiguration und Produktionsgröße

Chrome DevTools öffnen – und da liegt eine 3,5 MB große CSS-Datei.

Eine frisch deployte Feature-Seite: Ladezeit von 800 ms auf 3,2 Sekunden. Nach dem Debugging war klar: Das CSS war voll mit ungenutzten Tailwind-Klassen.

Tailwind gilt als „performance-freundlich“ – und trotzdem bremst es?

Später zeigte sich: Nicht Tailwind war schuld, sondern die Konfiguration. Wer JIT versteht, content richtig setzt und Produktion in mehreren Stufen optimiert, drückt CSS von MB auf KB – Netflix kommt mit 6,5 KB aus.

Hier die Stolpersteine und Lösungen aus der Praxis.


1. JIT-Modus: Tailwinds Performance-Wende

1.1 Problem des klassischen Modus

Bevor die 3,5-MB-CSS-Datei auftauchte, dachte ich bei Tailwind vor allem an „utility-first CSS“.

Der klassische Modus vor Tailwind v2 generierte alle denkbaren Klassenkombinationen – Farben, Abstände, Varianten (hover, focus, disabled …). In mittelgroßen Projekten wurden Dev-CSS-Dateien leicht 10 MB und mehr.

10 MB+
Dev-CSS-Volumen im klassischen Modus

10 MB CSS wirken lokal harmlos – Bandbreite ist kein Thema. Der Browser muss das Stylesheet aber parsen; Speicher und DevTools leiden.

In Firefox beim Debuggen: Klassenänderung → DevTools hängen Sekunden, Reload fühlt sich endlos an.

Im klassischen Modus zögert man bei Config-Änderungen: neuer Breakpoint, focus-visible – zuerst rechnen, wie viele Kombinationen dazukommen. Teams wählen zwischen Performance und Flexibilität.

1.2 So funktioniert JIT

JIT (Just-in-Time) kam mit Tailwind v2.1, ab v3 Standard. Kurz: On-Demand-Generierung.

Klassisch: alle Kombinationen beim Build – auch ungenutzte. JIT: Templates scannen (HTML, JSX, Vue …), nur genutzte Klassen generieren.

Beispiel klassisch:

.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }
.focus\:bg-black:focus { background-color: #000 }
.disabled\:bg-black:disabled { background-color: #000 }
/* ... Dutzende Varianten */

Nur bg-black im Projekt – trotzdem alle Varianten im CSS.

Mit JIT: Scan findet bg-black und hover:bg-black – nur diese zwei:

.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }

Dev-CSS sinkt von 10-MB-Niveau auf KB – gleiche Größe wie in Produktion.

1.3 Praktischer Nutzen

Beim ersten JIT-Refresh im Projekt wirkte der Seitenreload fast unrealistisch schnell – vorher Sekunden, jetzt fast instant.

Build-Geschwindigkeit: Voller Build früher 2–3 Minuten, jetzt Sekunden. Keine Vor-Generierung aller Styles, nur Template-Scan.

Dev-Erfahrung: DevTools flüssig. Früher parste der Browser ein 10-MB-Stylesheet bei jeder Änderung; jetzt wenige KB, Änderungen fast sofort sichtbar.

Arbitrary Values: Überraschung im positiven Sinn. Klassisch brauchte text-[#facc15] oft safelist. JIT unterstützt direkt:

// Keine Vordefinition in der Config nötig
<h1 class="text-[2.5rem] mt-[1.35rem] text-[#facc15]">
  JIT macht es einfach
</h1>

Dynamische Klassen: Klassisch 'text-' + color unsichtbar für den Scanner. JIT hat Grenzen, mit safelist aber flexibler.

JIT aktivieren? Ab Tailwind v3+ Standard, keine Extra-Schritte. Bei v2:

// tailwind.config.js
module.exports = {
  mode: 'jit',  // v2: manuell aktivieren
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
  // ...
}

2. content-Konfiguration: präzises Scannen

JIT hilft nur mit korrektem content.

content legt fest, welche Dateien nach Klassennamen gescannt werden. Zu eng → fehlende Styles. Zu weit → aufgeblähtes CSS.

2.1 Grundlagen

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{html,js,jsx,ts,tsx}',  // alle Template-Dateien unter src
  ],
  // ...
}

** = beliebige Verzeichnistiefe; *.{html,js,jsx,ts,tsx} = passende Endungen.

Framework-spezifisch anpassen:

// Next.js
content: [
  './pages/**/*.{js,ts,jsx,tsx}',
  './components/**/*.{js,ts,jsx,tsx}',
  './app/**/*.{js,ts,jsx,tsx}',  // App Router
]

// Astro
content: [
  './src/**/*.{astro,html,js,jsx,ts,tsx}',
]

Alle Dateien mit Tailwind-Klassen abdecken. Ein fehlendes Verzeichnis → Styles dort fehlen.

2.2 Typische Fallen

Falle 1: zu breites Glob

Einstweilen „einfach“ so konfiguriert:

content: [
  './**/*.js',  // scannt node_modules!
]

Tailwind las node_modules komplett – Build-Zeit explodierte, sinnlose Styles im CSS.

Besser Bereich begrenzen:

content: [
  './src/**/*.js',
  './components/**/*.js',
]

Falle 2: Komponenten-Verzeichnis vergessen

Nach Refactoring lagen Komponenten unter ./lib/components/. content nicht angepasst – alle Styles dort weg.

Stunden Debugging. Merksatz: Projektstruktur ändern → content mitziehen.

Falle 3: dynamisch gebaute Klassen

const color = 'red';
const className = `text-$&#123;color&#125;-500`;  // JIT erkennt das nicht

Im Quelltext steht ein Template-String, kein vollständiger Klassenname – Produktion droppt den Style.

Lösung: safelist (später) oder Objekt-Syntax:

const colors = {
  red: 'text-red-500',
  blue: 'text-blue-500',
};
const className = colors[color];  // vollständiger Name, erkennbar

2.3 safelist und dynamische Klassen

Wo dynamische Klassen nötig sind, hilft safelist:

// tailwind.config.js
module.exports = {
  safelist: [
    'text-red-500',
    'text-blue-500',
    'bg-red-500',
    // oder Regex für Klassengruppen
    {
      pattern: /text-(red|blue|green)-(500|600)/,
      variants: ['hover', 'focus'],
    },
  ],
}

safelist erzwingt Generierung auch ohne direktes Vorkommen in Templates.

Aber: mehr safelist → größeres CSS. Nur wo nötig – kein Sammelbecken für alle denkbaren Klassen.


3. Produktionsgröße: vierstufige Strategie

JIT hält Dev-CSS klein; Produktion braucht zusätzliche Schritte.

Vier Stufen – von Config bis Komprimierung:

Stufe 1
Präzises content
Nur genutzte Dateien scannen
Stufe 2
PurgeCSS
Ungenutzte Styles entfernen
Stufe 3
cssnano Minify
CSS komprimieren
Stufe 4
Brotli/Gzip
Transport-Komprimierung
Source: Optimierungsstufen

3.1 Stufe 1: präzises content

Grundlage – siehe oben.

Prinzip: nur Dateien mit Tailwind-Klassen scannen. Enger Scope → schnellerer Build, kleineres CSS.

// gut: enger Scope
content: [
  './src/components/**/*.jsx',
  './src/pages/**/*.tsx',
]

// schlecht: zu breit
content: [
  './**/*.js',  // scannt node_modules
]

3.2 Stufe 2: PurgeCSS

Tailwind v3+ aktiviert PurgeCSS im Produktions-Build automatisch.

Wichtig: Dev vs. Produktion sauber trennen:

# Dev (kein Purging)
npm run dev

# Produktion (automatisches PurgeCSS)
npm run build

Mit PostCSS explizit steuern:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
  },
}

3.3 Stufe 3: cssnano Minify

Nach PurgeCSS komprimiert cssnano weiter: Kommentare weg, Regeln zusammenführen, Selektoren und Werte vereinfachen.

# Tailwind CLI direkt minifizieren
npx tailwindcss -i ./src/input.css -o ./dist/output.css --minify

# oder über PostCSS (siehe oben)

Praxis: 150 KB nach PurgeCSS → 45 KB nach Minify.

3.4 Stufe 4: Brotli/Gzip

Keine CSS-Optimierung im engeren Sinn, sondern Transport.

Brotli oder Gzip auf dem Server – CSS oft 60–80 % kleiner über das Netz.

# Nginx Brotli (Modul ngx_brotli nötig)
brotli on;
brotli_comp_level 6;
brotli_types text/css application/javascript;

# oder Gzip (Nginx-Standard)
gzip on;
gzip_comp_level 6;
gzip_types text/css application/javascript;
6,5 KB
Netflix CSS Übertragungsgröße

45 KB CSS → etwa 8 KB übertragen mit Brotli.

Netflix’ 6,5 KB beziehen sich auf Brotli-Übertragung, nicht auf die Rohdatei.


4. Praxis: Vorher/Nachher und Troubleshooting

4.1 Optimierung im Vergleich

3,5 MB → 28 KB
Dev-CSS
vor/nach JIT
320 KB → 48 KB
Produktions-CSS
unkomprimiert
7,2 KB
finale Übertragung
nach Brotli
3,2 s → 820 ms
Lighthouse LCP
Seitenperformance
Source: Gemessene Werte

Bei diesem Vergleich war die Wirkung selbst überraschend deutlich.

4.2 Häufige Probleme

Styles fehlen?

  1. content prüfen – alle Template-Dateien im Scan?
  2. Dynamische Klassen → safelist nötig?
  3. Produktions-Build (npm run build), nicht Dev?

CSS immer noch groß?

safelist zu groß? Fremd-CSS im Tailwind-Output? content zu breit?

DevTools hängen?

Tailwind ≥3 (JIT Standard)? content enger? Bei v2: upgraden oder JIT manuell.


5. Ausblick: Tailwind v4

Nach der bestehenden Optimierung – was v4 (Ende 2024) bringt.

5.1 Oxide-Engine

Tailwind hat die Engine in Rust neu geschrieben.

182×
schnellere inkrementelle Builds

Offiziell: inkrementelle Builds bis zu 182× schneller. Klassenänderung: früher Sekunden Recompile, jetzt Millisekunden.

Oxide cached Scan-Ergebnisse und verarbeitet nur Geändertes. Bei 200+ Komponenten: früher ~10 s Build, jetzt Sekunden.

5.2 Zero-Config-Ziel

v4: die meisten Projekte brauchen kein tailwind.config.js.

Defaults decken Moderne ab: Container Queries, sinnvolle Breakpoints, Farbsystem. Config nur bei tiefer Anpassung.

Schnellerer Einstieg, weniger Config-Fehler.

Migration beachten: Syntax geändert – z. B. theme.extend.colors über CSS Custom Properties. Vor dem Upgrade die offizielle Migrationsanleitung lesen.


Fazit

Nach dem nächtlichen Crash habe ich die Tailwind-Config von Grund auf durchgearbeitet.

JIT hält Dev und Produktion gleich schlank; content steuert den Scan; vier Stufen drücken CSS von MB auf KB. Mit v4 Oxide ist Build-Geschwindigkeit selten noch der Engpass.

Wer noch im klassischen Modus arbeitet oder unsicher bei content ist – diese Punkte zuerst:

  1. Tailwind ≥3 (JIT Standard)
  2. content deckt alle Templates präzise ab
  3. Produktion mit PurgeCSS und cssnano
  4. Server mit Brotli/Gzip

Danach ist spürbarer Performance-Gewinn wahrscheinlich.

Fragen gerne in den Kommentaren – die offizielle Tailwind-Doku zu JIT und Produktionsoptimierung ist ebenfalls klar geschrieben.



Referenzen

Tailwind CSS Performance-Optimierung konfigurieren

Vom JIT-Modus bis zur Kontrolle der Produktionsgröße – der komplette Konfigurationsablauf

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Tailwind-Version prüfen

    Tailwind-CSS-Version im Projekt prüfen:

    • Tailwind v3+: JIT standardmäßig aktiv
    • Tailwind v2: mode: 'jit' manuell in der Config setzen
    • Upgrade auf v3+ empfohlen für beste Performance
  2. 2

    Step 2: content-Scan-Pfade konfigurieren

    Präzise Scan-Pfade in tailwind.config.js:

    • Next.js: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}']
    • Astro: ['./src/**/*.{astro,html,js,jsx,ts,tsx}']
    • Vermeiden: zu breite Glob-Muster wie './**/*.js'
  3. 3

    Step 3: Dynamische Klassennamen behandeln

    Für dynamisch gebaute Klassen safelist nutzen:

    • Vollständige Klassennamen im safelist-Array
    • pattern mit Regex für Klassengruppen
    • variants für hover, focus usw.
  4. 4

    Step 4: Produktions-Build optimieren

    cssnano in der PostCSS-Config:

    • Dev: cssnano aus (schnellerer Build)
    • Produktion: cssnano minify automatisch
    • Steuerung über process.env.NODE_ENV
  5. 5

    Step 5: Server-Komprimierung einrichten

    Brotli oder Gzip in Nginx:

    • Brotli: brotli on; brotli_comp_level 6;
    • Gzip: gzip on; gzip_comp_level 6;
    • Beide für text/css und application/javascript

FAQ

Wie aktiviert man JIT in Tailwind v3?
Tailwind v3+ nutzt JIT standardmäßig – keine Extra-Konfiguration. Bei Tailwind v2 mode: 'jit' in tailwind.config.js setzen.
Was passiert, wenn content Dateien auslässt?
Fehlende Pfade → Tailwind-Klassen in diesen Dateien werden nicht generiert, Styles fehlen im Produktions-Build. Bei Projektumstrukturierung content synchron anpassen.
Warum übersieht JIT dynamische Klassennamen?
JIT scannt Quelltext. Dynamisch zusammengesetzte Namen wie `text-$&#123;color&#125;-500` sind Template-Strings, keine vollständigen Klassen – nicht erkennbar. Lösung: safelist oder Objekt-Mapping.
Sind Netflix' 6,5 KB CSS roh oder komprimiert?
6,5 KB ist die übertragene Größe nach Brotli. Roh nach PurgeCSS und cssnano oft einige Dutzend KB – Brotli/Gzip reduziert die Übertragung deutlich.
Was bringt die Oxide-Engine in Tailwind v4?
Oxide ist in Rust neu geschrieben – inkrementelle Builds bis zu 182× schneller. Scan-Ergebnisse werden gecacht, nur geänderte Teile neu verarbeitet; große Projekte von ~10 s auf Sekunden.
Welche Folgen hat eine zu große safelist?
safelist erzwingt Style-Generierung – zu viele Einträge blähen CSS auf. Nur bei nötigen dynamischen Klassen nutzen, nicht alle denkbaren Klassen eintragen.

6 Min. Lesezeit · Veröffentlicht am: 30. März 2026 · Aktualisiert am: 9. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog