wenn Klassen aus fremden Quellen im Build fehlen
Klassennamen, die aus einem Headless-CMS-Feld, einer MDX-Datei oder einer npm-Komponentenbibliothek stammen, liegen oft außerhalb der Reichweite des Tailwind-Scanners. Wer nicht versteht, warum Content-Scanning genau dort scheitert, verliert Zeit mit rätselhaftem fehlendem CSS, obwohl die Klasse im Markup sichtbar dasteht.
Inhaltsverzeichnis
- 1. Warum Content aus fremden Quellen den Scanner umgeht
- 2. Headless-CMS-Felder: Klassen, die zur Laufzeit aus der API kommen
- 3. MDX-Dateien: Markdown mit eingebetteten Komponenten
- 4. Drittanbieter-Komponenten aus node_modules
- 5. Dynamisch zusammengesetzte Klassennamen im CMS-Kontext
- 6. Die @source-Direktive gezielt für externe Inhalte nutzen
- 7. Safelist-Strategien für CMS-gesteuerte Klassen
- 8. Das Mapping-Pattern: CMS-Werte auf feste Klassen abbilden
- 9. Lösungsansätze im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Content aus fremden Quellen den Scanner umgeht
Der Tailwind-Scanner arbeitet ausschließlich auf Dateien, die in der Content-Konfiguration per Glob-Pfad referenziert sind. Diese Grundannahme funktioniert hervorragend für klassischen Anwendungscode, stößt aber an ihre Grenzen, sobald Klassennamen aus Quellen stammen, die zur Build-Zeit gar nicht als Datei im Repository existieren. Ein Redakteur, der im Headless-CMS ein Feld mit dem Wert bg-sky-600 befüllt, erzeugt eine Zeichenkette, die erst zur Laufzeit über eine API-Antwort ins Frontend gelangt. Das Content-Scanning von Tailwind sieht diese Zeichenkette nie, weil sie zum Build-Zeitpunkt schlicht nicht existiert.
Dasselbe Grundproblem tritt bei MDX-Dateien mit dynamisch generiertem Content, bei npm-Komponentenbibliotheken ohne mitgelieferte CSS-Quelle und bei jedem anderen Szenario auf, in dem Klassennamen außerhalb der vom Entwicklerteam kontrollierten Quelldateien entstehen. Das Content-Scanning ist dabei kein Bug, sondern folgt konsequent dem Architekturprinzip, dass Tailwind zur Build-Zeit statischen Text analysiert. Wer diese Grenze versteht, kann gezielt gegensteuern, statt bei jedem fehlenden Style von vorne zu debuggen.
2. Headless-CMS-Felder: Klassen, die zur Laufzeit aus der API kommen
Ein besonders häufiger Fallstrick entsteht, wenn ein Headless-CMS wie Contentful, Sanity oder Strapi Redakteuren erlaubt, Freitextfelder mit Tailwind-Klassennamen zu befüllen, etwa für die Hintergrundfarbe eines Hero-Banners. Aus Entwicklersicht wirkt das flexibel: Der Redakteur kann die Optik ohne Deploy anpassen. Für das Content-Scanning ist das jedoch unsichtbar, weil der CMS-Inhalt niemals als Quelldatei im Repository landet, sondern nur zur Laufzeit über die API geladen wird.
Das Resultat: Die Komponente rendert class="{cmsField.bgColor}", der Wert aus dem CMS lautet bg-emerald-600, aber im generierten Stylesheet fehlt diese Klasse komplett, weil sie zum Build-Zeitpunkt in keiner gescannten Datei vorkam. Das Content-Scanning kann diese Lücke nicht selbst schließen, weil es keine Netzwerkanfragen absetzt und keine CMS-API kennt. Die Lösung muss auf Entwicklerseite ansetzen, nicht im Scanner selbst.
// PROBLEM: class name comes from a CMS API response at runtime,
// so it never appears as static text at build time
async function HeroBanner({ entryId }) {
const entry = await cms.getEntry(entryId);
// entry.fields.bgColorClass === "bg-emerald-600" (editor-controlled)
return <div className={`hero ${entry.fields.bgColorClass}`}>...</div>;
}
// The Oxide Engine's content scanner never sees "bg-emerald-600" because
// it only exists inside a runtime API response, not in any source file.
3. MDX-Dateien: Markdown mit eingebetteten Komponenten
MDX-Dateien vermischen Markdown-Prosa mit eingebetteten JSX-Komponenten und werden von vielen Content-Plattformen und Dokumentations-Sites verwendet. Solange MDX-Dateien im Repository liegen und von der Content-Konfiguration erfasst werden, funktioniert das Content-Scanning problemlos, weil die Oxide Engine MDX als reinen Text behandelt und darin nach Klassennamen sucht, genau wie in jeder anderen Datei. Der Fallstrick entsteht, wenn MDX-Content nicht statisch im Repository liegt, sondern zur Build-Zeit aus einem externen Content-Repository, einem Git-Submodul oder einem separaten Content-Paket bezogen wird, dessen Pfad die Content-Konfiguration nicht erfasst.
Ein zweites MDX-spezifisches Problem betrifft Custom-Components, die per Props gesteuerte Klassennamen erzeugen, etwa eine <Callout variant="warning" />-Komponente, die intern je nach variant-Prop unterschiedliche Tailwind-Klassen zusammensetzt. Solange diese Zusammensetzung als vollständige Zeichenketten im Komponenten-Code steht, findet das Content-Scanning sie zuverlässig. Wird die Klasse jedoch dynamisch aus dem Prop-Wert konkateniert, etwa ` + "`bg-${variant}-100`" + `, entsteht dieselbe Lücke wie bei CMS-Feldern.
// WRONG: dynamic concatenation breaks static scanning
function Callout({ variant }) {
return <div className={`p-4 rounded-lg bg-${variant}-100 border-${variant}-300`}>
{children}
</div>;
}
// RIGHT: fully written class strings per variant, scanner finds all of them
const VARIANT_CLASSES = {
warning: "p-4 rounded-lg bg-amber-100 border-amber-300",
danger: "p-4 rounded-lg bg-red-100 border-red-300",
info: "p-4 rounded-lg bg-sky-100 border-sky-300",
};
function Callout({ variant }) {
return <div className={VARIANT_CLASSES[variant]}>{children}</div>;
}
4. Drittanbieter-Komponenten aus node_modules
Ein weiterer Fallstrick beim Content-Scanning betrifft npm-Pakete, die Tailwind-Klassennamen in ihrem kompilierten Code mitliefern, etwa eine UI-Bibliothek, deren Komponenten selbst mit Tailwind gebaut wurden. Standardmäßig schließt die Content-Konfiguration node_modules aus Performance-Gründen aus, was bedeutet, dass Klassennamen innerhalb der Bibliothek nicht gescannt werden, selbst wenn das eigene Projekt dieselbe Tailwind-Installation verwendet.
Das Ergebnis: Die Bibliothekskomponente rendert korrekt strukturiertes Markup mit gültigen Tailwind-Klassen, aber weil das Content-Scanning das Paket nie erfasst hat, fehlen genau diese Klassen im generierten CSS des Hauptprojekts. Manche UI-Bibliotheken lösen das, indem sie ihr eigenes, vorkompiliertes CSS mitliefern, das unabhängig vom Scan-Ergebnis des Hauptprojekts eingebunden wird. Andere Bibliotheken empfehlen explizit, ihren dist-Ordner in die eigene Content-Konfiguration aufzunehmen, damit das Content-Scanning auch deren Klassen erfasst.
// package.json of a hypothetical UI library — two competing strategies
// Strategy A: ship precompiled CSS, no scanning needed by consumers
// import "@acme/ui-kit/dist/styles.css";
// Strategy B: ship raw source, consumer must extend their own
// content configuration to cover the library's component sources
// @source "../../node_modules/@acme/ui-kit/src/**/*.tsx";
5. Dynamisch zusammengesetzte Klassennamen im CMS-Kontext
Ein Muster, das in CMS-getriebenen Projekten besonders häufig zu Problemen führt, ist die String-Interpolation basierend auf redaktionell gepflegten Werten, etwa einer Spaltenanzahl, die im CMS als Zahl gespeichert und dann in eine Grid-Klasse umgewandelt wird. Der Code ` + "`grid-cols-${columns}`" + ` sieht harmlos aus, erzeugt aber für das Content-Scanning dasselbe Problem wie jede andere dynamische Konkatenation: Der Scanner kann die konkrete Zahl aus dem CMS zum Build-Zeitpunkt nicht kennen und daher auch keine vollständige Klasse ableiten.
Die praktikable Lösung besteht darin, redaktionell erlaubte Werte im CMS selbst auf eine begrenzte Auswahl einzuschränken, etwa über ein Dropdown mit den Optionen 2, 3 oder 4 Spalten, und im Code eine explizite Zuordnungstabelle zu pflegen, die für jeden möglichen Wert eine vollständig ausgeschriebene Klasse bereithält. Diese Einschränkung mag auf den ersten Blick nach Komforteinbuße für Redakteure aussehen, verhindert aber zuverlässig, dass das Content-Scanning unvorhersehbare Werte verpasst.
6. Die @source-Direktive gezielt für externe Inhalte nutzen
Tailwind CSS v4 bietet mit der @source-Direktive eine gezielte Möglichkeit, zusätzliche Pfade in das Content-Scanning aufzunehmen, die von der automatischen Erkennung nicht erfasst werden. Das betrifft insbesondere Fälle, in denen MDX-Content aus einem externen Repository per Git-Submodul eingebunden wird oder eine npm-Bibliothek ihre Quelldateien in einem unüblichen Pfad ablegt. Mit @source "../content-repo/**/*.mdx" lässt sich ein komplett separates Verzeichnis explizit in den Scan-Vorgang einbeziehen, unabhängig davon, ob es innerhalb oder außerhalb des eigentlichen Projektordners liegt.
Wichtig dabei: Die @source-Direktive erweitert nur, welche Dateien gescannt werden, sie löst aber nicht das grundsätzliche Problem von Klassennamen, die erst zur Laufzeit über eine API entstehen. Für CMS-Felder mit Freitext-Klassennamen bleibt die @source-Direktive wirkungslos, weil kein Dateipfad im Repository existiert, der diese Werte enthält. Sie ist das richtige Werkzeug für extern liegenden, aber statisch vorhandenen Content, nicht für zur Laufzeit generierte Werte.
/* tailwind.config.css — extend scanning to externally sourced content */
@import "tailwindcss";
@source "./src/**/*.{ts,tsx}";
/* MDX content pulled in via a git submodule outside the app's own folder */
@source "../content-repo/**/*.mdx";
/* Third-party component library's dist folder, not covered by default rules */
@source "../../node_modules/@acme/ui-kit/dist/**/*.js";
7. Safelist-Strategien für CMS-gesteuerte Klassen
Wo @source nicht greift, weil Klassennamen ausschließlich zur Laufzeit über eine API entstehen, ist die Safelist der richtige Mechanismus für zuverlässiges Content-Scanning-Verhalten. Die Safelist erzwingt die Generierung bestimmter Klassen unabhängig vom Scan-Ergebnis, indem sie explizit im CSS-Entry-Point aufgeführt werden. Für CMS-gesteuerte Freitextfelder empfiehlt es sich, alle theoretisch möglichen Werte, die Redakteure im CMS auswählen können, in die Safelist aufzunehmen.
Wichtig ist dabei, die Safelist nicht als Blankoscheck zu verstehen: Jede zusätzliche Safelist-Klasse landet im finalen Stylesheet, unabhängig davon, ob sie jemals verwendet wird. Eine unkontrolliert wachsende Safelist untergräbt genau den Vorteil, den Content-Scanning eigentlich bieten soll, nämlich ein CSS-Bundle, das exakt die tatsächlich genutzten Klassen enthält. Die Safelist sollte deshalb eng an die begrenzte Werteliste im CMS gekoppelt sein, nicht als pauschale Absicherung für beliebige zukünftige Werte dienen.
/* tailwind.config.css — safelist for CMS-driven background colors */
@import "tailwindcss";
/* Only the exact set of values editors can pick in the CMS dropdown */
@source inline("bg-sky-{100,600}");
@source inline("bg-emerald-{100,600}");
@source inline("bg-amber-{100,600}");
@source inline("bg-red-{100,600}");
8. Das Mapping-Pattern: CMS-Werte auf feste Klassen abbilden
Die robusteste Lösung für Content-Scanning-Probleme mit CMS-Daten ist meist gar nicht die Safelist, sondern ein Mapping-Pattern im Anwendungscode: Statt den CMS-Wert direkt als Klassennamen zu verwenden, wird er als semantischer Schlüssel behandelt, der über eine im Code definierte Tabelle auf eine vollständig ausgeschriebene Tailwind-Klasse abgebildet wird. Der Redakteur wählt im CMS beispielsweise "accent" statt einer konkreten Farbklasse, und der Code übersetzt "accent" in bg-sky-600.
Dieses Pattern hat zwei Vorteile gegenüber der reinen Safelist: Erstens findet das Content-Scanning die vollständige Klasse direkt im Quellcode der Mapping-Tabelle, ganz ohne zusätzliche Safelist-Einträge. Zweitens entkoppelt es das CMS-Datenmodell vollständig von Tailwind-internen Klassennamen, sodass ein späterer Wechsel der Farbpalette nur eine Änderung an einer zentralen Stelle im Code erfordert, statt in jedem CMS-Eintrag manuell nachgepflegt werden zu müssen.
9. Lösungsansätze im Vergleich
Für jedes der beschriebenen Content-Scanning-Probleme gibt es mehrere denkbare Lösungswege mit unterschiedlichen Kompromissen bei Wartbarkeit, Sicherheit und Aufwand.
| Szenario | Unsicher | Empfohlene Lösung | Vorteil |
|---|---|---|---|
| CMS-Freitextfeld | Klasse direkt aus API | Mapping-Pattern im Code | Klasse steht im Quellcode, kein Verlust möglich |
| MDX aus externem Repo | Ignoriert, nicht gescannt | @source auf externen Pfad | Vollständige Erfassung ohne manuelle Pflege |
| npm-UI-Bibliothek | node_modules ignoriert | dist-Ordner explizit einbeziehen | Bibliotheks-Klassen im eigenen Build enthalten |
| Dynamische Spaltenzahl | Template-Literal-Konkatenation | Feste Werteliste + Zuordnungstabelle | Begrenzte, vorhersehbare Werte |
In allen vier Szenarien liegt die nachhaltigste Lösung im Anwendungscode selbst, nicht in der Tailwind-Konfiguration allein. Die Safelist bleibt ein legitimes Werkzeug für Randfälle, sollte aber nie die erste Wahl sein, wenn ein Mapping-Pattern oder eine erweiterte Content-Direktive dasselbe Ergebnis mit weniger CSS-Overhead erreicht.
Mironsoft
Headless-CMS-Integrationen, MDX-Content und Tailwind-Architektur
Fehlende CSS-Klassen aus CMS oder MDX-Content?
Wir analysieren, warum bestimmte Tailwind-Klassen im Build fehlen, richten Mapping-Pattern und gezielte Content-Direktiven ein und sorgen dafür, dass Redakteure Inhalte pflegen können, ohne das CSS-Bundle unkontrolliert wachsen zu lassen.
CMS-Audit
Redaktionelle Freitextfelder identifizieren, die Tailwind-Klassen enthalten
Mapping-Pattern
CMS-Werte sauber auf feste, im Code sichtbare Klassennamen abbilden
Content-Konfiguration
@source-Direktiven für externe MDX-Repos und npm-Bibliotheken einrichten
10. Zusammenfassung
Fallstricke im Content-Scanning entstehen fast immer dort, wo Klassennamen aus Quellen kommen, die zur Build-Zeit nicht als statischer Text im Repository vorliegen: Headless-CMS-Felder, extern eingebundene MDX-Dateien, npm-Komponentenbibliotheken und dynamisch zusammengesetzte Werte teilen dasselbe Grundproblem. Der Tailwind-Scanner kann nur finden, was zur Build-Zeit als vollständige Zeichenkette existiert, und keine API-Antworten oder Laufzeit-Berechnungen antizipieren.
Die nachhaltigste Lösung ist meist ein Mapping-Pattern, das CMS-Werte auf feste, im Quellcode sichtbare Klassennamen abbildet. Für extern liegenden, aber statisch vorhandenen Content hilft die @source-Direktive. Die Safelist bleibt als letzter Ausweg für echte Freitextfälle sinnvoll, sollte aber eng begrenzt bleiben, damit das Content-Scanning weiterhin ein schlankes, bedarfsgerechtes CSS-Bundle liefert.
Content-Scanning Fallstricke — Das Wichtigste auf einen Blick
CMS-Felder
Klassennamen aus API-Antworten werden vom Scanner nie erfasst, weil sie zur Build-Zeit nicht existieren.
Mapping-Pattern
CMS-Werte als semantische Schlüssel behandeln, die im Code auf vollständige Klassen abgebildet werden.
@source-Direktive
Externe MDX-Repos und npm-Bibliotheken explizit in den Scan-Vorgang aufnehmen.
Safelist mit Maß
Nur für echte Freitextfälle nutzen und eng an die tatsächlich möglichen Werte koppeln.