WP Rocket Remove Unused CSS: warum Styles verschwinden und wie man sie zurückholt

WP Rocket Remove Unused CSS ist eines der wirkungsvollsten Features in WP Rocket. Es ist auch eines der wenigen, die eine Website zuverlässig kaputt machen können, ohne dass eine einzige Fehlermeldung erscheint. Besonders tückisch: Der Fehler tritt oft nicht sofort auf, sondern Tage oder Wochen später, scheinbar ohne Zusammenhang mit einer Änderung. Dieser Artikel erklärt die häufigste Ursache, zeigt die Diagnose und stellt eine Lösung vor, die besser funktioniert als der übliche Griff zur Safelist.

Veröffentlicht am

Lesezeit

WordPress

Was Remove Unused CSS technisch macht

Remove Unused CSS entfernt alle Stylesheets und alle Inline-Styles aus einer Seite und ersetzt sie durch einen einzigen <style>-Block im <head>, erkennbar an der ID wpr-usedcss. In diesem Block landet ausschließlich das CSS, das auf dieser konkreten Seite tatsächlich gebraucht wird.

Um zu entscheiden, was gebraucht wird, ruft WP Rocket die Seite über seine Cloud-Server ab. Der Abruf erfolgt mit angehängten Parametern, die Cache und Optimierungen umgehen:

?wpr_imagedimensions=1&nowprocket=1&no_optimize=1

Der Generator vergleicht dann jeden Selektor aus dem CSS mit dem DOM, das er in dieser HTML-Antwort vorfindet. Was matcht, bleibt. Was nicht matcht, fliegt raus.

Genau hier liegt das Problem.

Warum per JavaScript eingefügte Klassen verschwinden

Der Generator führt kein JavaScript aus. Er sieht das HTML so, wie der Server es ausliefert, nicht so, wie es im Browser nach dem Rendern aussieht.

Jede CSS-Klasse, die erst durch JavaScript in den DOM gelangt, existiert für den Generator schlicht nicht. Und was nicht existiert, wird als „ungenutzt” eingestuft und entfernt. Typische Kandidaten:

  • Statusklassen wie .is-active, .is-open, .is-sticky, die per Klick oder Scroll gesetzt werden
  • Slider und Karussells, die ihre Markup-Struktur selbst aufbauen (.swiper-slide-active und Verwandte)
  • Popover, Modals, Dropdowns, deren Inhalt erst beim Öffnen erzeugt wird
  • AJAX-Nachladungen, etwa gefilterte Listen oder Kalender
  • Animations-Bibliotheken, die beim Eintritt in den Viewport Klassen setzen

Ein besonders unangenehmer Sonderfall: Eine Klasse steht zwar im HTML, aber nur unter bestimmten Bedingungen. Ein Kalender-Widget, das in einem Monat ohne Termine gerendert wird, gibt die Klasse .has-event nicht aus. Der Generator sieht sie nicht, entfernt die zugehörigen Regeln – und die Termindarstellung ist auf dieser Seite dauerhaft kaputt, obwohl sie auf anderen Seiten funktioniert.

WP Rocket Remove Unused CSS: Warum der Fehler oft verzögert auftritt

Das erklärt auch das Phänomen, dass eine Seite monatelang korrekt aussieht und dann plötzlich nicht mehr. WP Rocket regeneriert das Used CSS automatisch alle 30 Tage neu, außerdem beim Bearbeiten eines Beitrags, beim Theme-Wechsel und bei Änderungen an der Safelist. Solange das alte, zufällig korrekte Used CSS ausgeliefert wurde, war alles in Ordnung. Nach der Regenerierung greift der Fehler.

WP Rocket Remove Unused CSS: Das Problem sauber diagnostizieren

Der entscheidende Punkt bei der Diagnose: Die Entwicklertools des Browsers sind hier keine verlässliche Quelle. Der Elements-Tab zeigt den DOM nach der JavaScript-Ausführung. Genau das, was WP Rocket eben nicht sieht.

Schritt 1: Ist WP Rocket Remove Unused CSS überhaupt die Ursache?

Die betroffene Seite mit ?nowprocket=1 aufrufen. Dieser Parameter umgeht WP Rocket vollständig.

Sieht die Seite damit korrekt aus, liegt die Ursache bei WP Rocket. Sieht sie weiterhin falsch aus, ist das Problem woanders und die Suche in der Safelist wäre verlorene Zeit.

Schritt 2: Welche Klasse fehlt wirklich?

Statt in die Entwicklertools zu schauen, wird das ausgelieferte HTML direkt geprüft:

curl -s https://example.com/die-seite/ | grep -c 'meine-klasse'

Steht die Klasse in den Entwicklertools, liefert curl aber 0 zurück, ist der Beweis erbracht: Die Klasse kommt aus JavaScript und der Generator kann sie nicht sehen.

Eine Stolperfalle dabei: Die verantwortliche Klasse sitzt häufig nicht auf dem sichtbar kaputten Element, sondern auf einem übergeordneten. Ein per JavaScript gesetztes .menu-open am <body> bringt die Regel .menu-open .submenu { … } zu Fall, obwohl .submenu brav im Markup steht. Die Eltern-Elemente also immer mitprüfen.

Schritt 3: Vergleich der beiden Fassungen

Wer es systematisch angehen will, zieht sich beide Varianten und vergleicht die enthaltenen Selektoren:

curl -s 'https://example.com/die-seite/?nowprocket=1&no_optimize=1' > raw.html
curl -s 'https://example.com/die-seite/' > opt.html

Was in raw.html an Selektoren vorkommt, in opt.html aber fehlt, ist die Liste der Kandidaten.

Lösung 1: Die CSS Safelist und warum sie oft die falsche Wahl ist

Der naheliegende Weg führt über WP Rocket > Dateioptimierung > Ungenutztes CSS entfernen > CSS-Safelist. Dort lassen sich Klassen, Dateipfade und Wildcards hinterlegen, die vom Entfernen ausgenommen werden:

.mein-element
(.*).is-(.*)
/wp-content/themes/mein-child-theme/style.css

Das funktioniert. Es hat allerdings zwei Nachteile, die selten erwähnt werden.

Die Safelist ist global. Sie gilt für jede einzelne Seite der Website. Wer eine Klasse aus einem Popover safelistet, das nur auf einer Landingpage vorkommt, schleppt dieses CSS ab sofort auf allen Seiten mit. Und da das Used CSS inline im HTML ausgeliefert wird und nicht als separate, vom Browser cachebare Datei, wird jedes zusätzliche Byte bei jedem einzelnen Seitenaufruf erneut übertragen. Bei einem kompletten safegelisteten Stylesheet summiert sich das.

Die Safelist wächst. Jede neue Statusklasse, jeder neue Slider, jede neue interaktive Komponente erfordert einen neuen Eintrag. Und jede Änderung an der Safelist löst eine vollständige Neuberechnung des Used CSS für die gesamte Website aus. Bei einer Seite mit einigen tausend URLs ist das nichts, was man mal eben nebenbei macht.

Für eine schnelle Prüfung ist die Safelist trotzdem nützlich: Wer testweise (.*) einträgt, safelistet alles. Ist das Layout danach korrekt, ist Remove Unused CSS bestätigt als Ursache. Bleibt es kaputt, liegt das Problem woanders. Ein häufiger Kandidat sind Syntaxfehler im CSS, an denen sich der Parser von WP Rocket verschluckt.

Lösung 2: Der DOM-Primer

Deutlich eleganter ist es, das Problem an der Wurzel zu lösen: Wenn der Generator die Klassen nicht sieht, dann zeigt man sie ihm einfach.

Dazu wird ein verstecktes Element ins Markup ausgegeben, das genau die Struktur enthält, die das JavaScript später erzeugt. Bei einer Komponente, die per Shortcode eingebunden wird, gehört dieser Block in den Shortcode:

function meine_komponente_dom_primer() {
    return '
    <div class="komponente-primer" hidden aria-hidden="true" inert>
        <div class="meine-komponente">
            <img src="" alt="" class="meine-komponente__image">
            <div class="meine-komponente__date"></div>
            <div class="meine-komponente__title"></div>
            <a href="" class="meine-komponente__link">Ansehen</a>
        </div>
        <div class="meine-komponente__overlay"></div>
    </div>';
}

Zur Sicherheit gehört ins Stylesheet noch:

.komponente-primer { display: none !important; }

Das Ergebnis: Der Generator findet alle Selektoren und behält die zugehörigen Regeln. Und zwar ausschließlich auf den Seiten, auf denen der Shortcode tatsächlich eingebunden ist. Genau das, was die globale Safelist nicht leisten kann.

Warum kein <template>?

Der Reflex, dafür ein <template>-Element zu verwenden, ist naheliegend, aber riskant. Der Inhalt eines <template> liegt per Spezifikation nicht im Dokumentbaum, sondern in einem separaten DocumentFragment. Ein document.querySelectorAll('.meine-komponente__title') findet darin nichts. Ob der Generator die Klassen trotzdem erkennt, hängt davon ab, ob er den DOM parst oder nur das rohe HTML nach Klassennamen durchsucht und darauf sollte man sich nicht verlassen. Ein normales, verstecktes div ist eindeutig und funktioniert garantiert.

Den Primer gleich sinnvoll nutzen

Wenn das Markup ohnehin im DOM steht, kann das JavaScript es klonen, statt HTML-Strings zusammenzubauen:

const tpl = document.querySelector('.komponente-primer .meine-komponente');

function buildMyComponent(ev) {
    const node = tpl.cloneNode(true);
    node.querySelector('.meine-komponente__title').textContent = ev.title;
    node.querySelector('.meine-komponente__link').setAttribute('href', ev.permalink);
    return node;
}

Das hat zwei angenehme Nebenwirkungen. Markup und CSS können nicht mehr auseinanderlaufen, weil es nur noch eine Quelle für die Struktur gibt. Und textContent escaped Inhalte automatisch. Wer stattdessen Strings mit innerHTML zusammensetzt, baut sich bei nutzergenerierten Inhalten eine XSS-Lücke ein.

Das Ergebnis prüfen

Nach der Änderung wird das Used CSS für die betroffene URL neu erzeugt: In der Admin-Leiste unter WP Rocket > Used CSS dieser URL leeren.

Wichtig ist, das Ergebnis nicht sofort zu bewerten. Bis die Neuberechnung abgeschlossen ist, wird weiterhin das alte Used CSS ausgeliefert. Eine Seite kann also kurzzeitig „repariert” aussehen, ohne dass die Änderung überhaupt gewirkt hat.

Erst wenn der wpr-usedcss-Block sich tatsächlich geändert hat, ist die Prüfung aussagekräftig:

curl -s https://example.com/die-seite/ \
  | grep -o '<style id="wpr-usedcss">.*</style>' \
  | grep -c 'meine-komponente__title'

Liefert das einen Wert größer null, ist der Selektor im Used CSS angekommen.

Fazit

WP Rocket Remove Unused CSS entfernt CSS, dessen Selektoren im ausgelieferten HTML nicht vorkommen. Klassen, die erst durch JavaScript entstehen, gehören dazu und weil der Fehler oft erst mit der automatischen Regenerierung nach 30 Tagen auftritt, wird er selten mit der eigentlichen Ursache in Verbindung gebracht.

Die Safelist ist dafür die schnelle, aber globale Lösung: Sie belastet jede Seite mit CSS, das nur einzelne Seiten brauchen, und sie wächst mit jeder neuen Komponente. Ein versteckter DOM-Primer im Shortcode oder Template löst dasselbe Problem lokal, wirkt nur dort, wo die Komponente auch eingebunden ist, und macht das JavaScript nebenbei sauberer.

Wenn deine Website nach dem Aktivieren von Remove Unused CSS Darstellungsfehler zeigt und du die Ursache nicht findest, kannst du dich gerne bei mir melden.