
🌐 Auch auf: English · Français · Español
☝️ Für alle, die das hier gerade auf dem Handy lesen: Bis diese Woche hat dir die Startseite dieselbe Handvoll Artikel viermal hintereinander gezeigt (ein Laufband, ein Slider, eine Tab-Box, eine „Trending“-Liste, ein „Featured“-Raster), bevor die eigentliche Liste überhaupt anfing. Jetzt bekommst du die fünf neuesten Beiträge, einen nach dem anderen, und das war’s. Am PC sieht alles aus wie vorher. Wenn du nichts gemerkt hast: perfekt, genau so war’s geplant. 📱
Jedes Theme sieht in seiner Demo auf einem 27-Zoll-Monitor großartig aus. Meins (MoreNews) hat eine volle Startseite im Zeitschriften-Stil: ein News-Laufband, einen „Main News“-Slider, eine Tab-Box mit Latest / Popular / Update, eine Spalte „Trending Now“, „Featured Posts“ und ganz unten „You may have missed“. Am Desktop stehen diese Blöcke nebeneinander und sehen aus wie eine Zeitung. Auf dem Handy stapelt das Theme jeden einzelnen Block unter den nächsten. Bei einem Blog, der ein paar Beiträge pro Woche veröffentlicht, heißt das vor allem eins: dieselben fünf Beiträge, immer und immer wieder, über mehrere Bildschirmlängen, bevor die echte Liste überhaupt beginnt.
Arbeitsteilung wie immer: Ich entscheide, mein KI-Co-Admin (Claude) hat die Screenshots gemacht, das CSS geschrieben, die Messungen durchgeführt und herausgefunden, warum die erste Messung schlechter statt besser aussah.

🧹 Die Lösung: eine Media Query, sechs Selektoren
Kein Plugin, kein Child-Theme, kein überschriebenes Template. Nur ein paar Zeilen CSS im „Zusätzlichen CSS“ von WordPress. Das liegt in der Datenbank und nicht im Theme-Ordner, ein Theme-Update kann es also nicht löschen:
@media screen and (max-width: 1024px) {
body.home .banner-exclusive-posts-wrapper,
body.home .aft-main-banner-section,
body.home .af-main-banner-featured-posts,
body.home .above-footer-widget-section,
body.home #secondary .widget:has(.wp-block-latest-posts),
body.home #secondary .widget:has(.wp-block-archives-list) { display: none !important; }
}- Warum 1024 px? Genau ab dieser Breite schaltet MoreNews seine Banner-Spalten auf volle Breite und fängt an, sie zu stapeln. Darüber bekommen Tablets im Querformat das normale Desktop-Layout, und das funktioniert dort gut.
- Warum
body.home? Betroffen ist nur die Startseite (samt Seite 2, 3, …). Einzelne Beiträge behalten ihre Sidebar und den Block „You may have missed“. - Warum
:has()für die Sidebar? Widget-IDs wie#block-3ändern sich, sobald jemand die Sidebar umsortiert. „Das Widget, das einen Latest-Posts-Block enthält“ bleibt gleich. Alle aktuellen Browser unterstützen:has(). - Was auf dem Handy übrig bleibt: die fünf neuesten Beiträge („Blogseiten zeigen maximal“ steht in WordPress auf 5), die Seitennavigation, die Suche und die Kategorien.
Und weil dieser Blog per Ansible verwaltet wird, steht das CSS nicht nur in der Datenbank: Das Playbook schreibt es bei jedem Lauf neu. Wenn jemand den Customizer „aufräumt“, setzt es der nächste Lauf wieder ein.
📉 Die Wendung: Die Seite wurde langsamer
Weniger Zeug auf der Seite, also muss sie schneller sein, oder? Lighthouse (mobil, je drei Durchläufe) sah das anders. Der Performance-Wert fiel von 93–95 auf 91, und die Zeit, bis das größte Element sichtbar ist (LCP), stieg von 2,6 s auf 3,4 s.
Der Grund ist eine schöne Lektion darüber, wie LCP funktioniert. Vorher war das größte Element auf dem Handybildschirm das Slider-Bild, und ein kleines Must-use-Plugin, das ich zwei Tage zuvor geschrieben hatte, sorgte dafür, dass genau dieses Bild sofort und mit hoher Priorität lädt. Jetzt ist der Slider ausgeblendet, und das größte Element ist das Bild des ersten Beitrags in der Liste. Das Theme markiert jedes Bild mit loading="lazy" („später laden, wenn es gleich ins Bild scrollt“), auch dieses. Also wartete der Browser 2,1 Sekunden, bevor er es überhaupt anforderte. Währenddessen priorisierte mein Plugin pflichtbewusst weiter das Bild eines Sliders, den niemand sehen konnte.
Korrektur Nummer eins: Auf der Startseite gibt das Plugin jetzt dem ersten Bild in der eigentlichen Beitragsliste „sofort laden, hohe Priorität“ (WordPress‘ in_the_loop() unterscheidet die Liste vom Banner, weil das Banner gerendert wird, bevor die Schleife startet). Das erste Banner-Bild bekommt nur noch „sofort laden“ ohne Prioritätsschub, für Besucher am Desktop, die es weiterhin sehen.
🕵️ Das Attribut, das WordPress heimlich verschluckt hat
Nach Korrektur Nummer eins lud das Bild sofort, aber das Attribut fetchpriority="high" stand einfach nicht im HTML. Rief man denselben Code auf der Kommandozeile auf, war es da. Auf der Live-Seite nicht.
Der Übeltäter: Das Theme gibt jedes Beitragsbild über wp_kses_post() aus, den HTML-Filter von WordPress. Dessen Liste erlaubter <img>-Attribute enthält kein fetchpriority, also wurde das Attribut stillschweigend entfernt. Keine Warnung, kein Logeintrag. Das heißt auch: Die „Performance-Optimierung“ von zwei Tagen zuvor hatte immer nur zur Hälfte gewirkt. Ein kleiner Filter, der fetchpriority zu den erlaubten Attributen hinzufügt, und es kommt endlich im Browser an.
📊 Die Zahlen
| Lighthouse, mobil | Vorher | Nur ausgeblendet | Endstand |
|---|---|---|---|
| Performance-Wert | 93–95 | 91 | 95–96 |
| Largest Contentful Paint | 2,6 s | 3,4 s | 2,7 s |
| Total Blocking Time | 150–200 ms | 44 ms | 30–50 ms |
| Übertragene Daten | 731 KB | 569 KB | 569 KB |
| Wartezeit, bis das LCP-Bild angefordert wird | – | 2,1 s | 0,7 s |
| Desktop-Wert | 100 | 100 | 100 |
Wie viel schneller ist es also? Ehrlich gesagt: Das große Bild erscheint ungefähr so schnell wie vorher. Über die absichtlich langsame Testverbindung von Lighthouse entfällt der Großteil dieser 2,7 Sekunden auf die Antwort des Servers (0,6 s) und den Download des Bildes selbst. Was sich wirklich geändert hat: 22 % weniger Daten, weil ausgeblendete Blöcke ihre Bilder gar nicht erst laden, und etwa viermal weniger JavaScript-Blockierzeit, die Seite reagiert also schneller, wenn du tippst oder scrollst. Und das Wichtigste: Du liest den neuesten Beitrag ohne zu scrollen statt nach drei Bildschirmen.
🖼️ Kleinere Bilder fürs Handy? Lohnt sich nicht
Die naheliegende nächste Idee: Handys kleinere Bilder ausliefern. Ich habe es geprüft und mich dagegen entschieden.
- Handys und PCs bekommen derzeit dieselbe Datei: ein 768 px breites AVIF, typischerweise 30–70 KB. Der Bildoptimierer erzeugt für seine AVIF-Versionen nur eine Größe; die WebP-Variante für ältere Browser gibt es bereits in vier Größen.
- Ein Handy, das 412 „CSS-Pixel“ breit ist, hat eine Pixeldichte von etwa 2,6, also rund 1.070 echte Pixel. Für ein scharfes, bildschirmbreites Bild braucht es mindestens so viele Pixel wie die Spalte am PC. 768 px liegen schon leicht darunter und sehen nur deshalb gut aus, weil AVIF so effizient ist.
- Profitieren würden nur alte Handys mit geringer Pixeldichte. Bestenfalls vielleicht 20–30 KB pro Bild, etwa 100 KB auf der ganzen Startseite. Dafür lohnen sich keine zusätzlichen Bildgrößen auf der Platte und noch mehr bewegliche Teile.
Der eigentliche Engpass war nie die Bildgröße. Sondern dass dem wichtigsten Bild gesagt wurde, es solle warten.
🤦 Bonus: Ich habe mich selbst ausgesperrt (schon wieder)
Für den „Vorher“-Screenshot haben wir eine gespeicherte Kopie der alten Startseite gerendert. Diese Kopie verwies noch auf minifizierte CSS- und JavaScript-Dateien, die das Optimierungs-Plugin Minuten zuvor beim Leeren seines Caches weggeworfen hatte. Ein Seitenaufruf, eine Salve „404 Not Found“ in wenigen Sekunden, und fail2ban hat genau das getan, wofür es gebaut wurde: Es hielt meine Heim-IP für einen Scanner und sperrte sie aus, Web und SSH gleichermaßen. Funktioniert wie vorgesehen. Leicht peinlich. Es wurden Tee und Geduld angewendet. ☕
🎨 Welches Theme, und was passiert, wenn ich mal wechsle?
Dieser Blog läuft mit MoreNews von AF themes, einem klassischen Magazin-Theme (kein Block-Theme). Das ist wichtig, denn das meiste von dem, was ich oben gemacht habe, hängt daran. Wenn ich eines Tages das Theme wechsle, kommt Folgendes mit und Folgendes nicht:
| Änderung | Übersteht einen Theme-Wechsel? | Warum |
|---|---|---|
| „5 Beiträge pro Seite“ | ✅ Ja | Eine WordPress-Einstellung (Einstellungen → Lesen), keine Theme-Einstellung. |
Die Freigabe für fetchpriority | ✅ Ja | Steckt in einem Must-use-Plugin, dem das Theme egal ist. Harmlos, falls das neue Theme sie nicht braucht. |
| „Erstes Listenbild lädt zuerst“ | ✅ Größtenteils | Nutzt nur WordPress-Standardfunktionen (in_the_loop(), die Standardklasse wp-post-image). Funktioniert mit jedem Theme, das Beitragsbilder in der Beitragsliste zeigt. |
| Schriften vorladen | ❌ Nein | Verweist auf die Schriftdateien im MoreNews-Ordner. Ein neues Theme hat andere Schriften: Pfade anpassen oder weglassen. |
| Banner-Blöcke ausblenden | ❌ Nein | Klassennamen wie .aft-main-banner-section gibt es nur bei MoreNews. Ein neues Theme hat eigene Blöcke mit eigenen Namen. |
| Recent Posts / Archive ausblenden | ⚠️ Teilweise | Die Block-Klassen kommen aus dem WordPress-Kern und bleiben gleich. #secondary als Sidebar-ID ist verbreitet, aber nicht garantiert. |
| Das „Zusätzliche CSS“ selbst | ❌ Nicht automatisch | WordPress speichert es pro Theme. Nach einem Wechsel startet das neue Theme mit einem leeren Feld (das alte CSS bleibt für MoreNews erhalten). |
Die Idee lässt sich also 1:1 übertragen: entscheiden, was ein Besucher auf dem Handy wirklich braucht, messen, das wichtigste Bild zuerst laden lassen. Die Selektoren muss man neu schreiben. Das dauert vielleicht zehn Minuten mit den Entwicklerwerkzeugen des Browsers: auf Handyansicht umschalten, Rechtsklick auf den unerwünschten Block, Klassennamen nachsehen.
Und mit einem modernen Block-Theme bräuchte man gar kein CSS: Im Website-Editor entfernt man solche Blöcke einfach aus der Startseiten-Vorlage. Das ist sogar besser als display: none, denn ein ausgeblendeter Block ist trotzdem Teil des HTML, das der Browser herunterlädt, ein entfernter wird gar nicht erst geschickt.
📂 Wo der Code liegt
Beide Änderungen gehören zur Ansible-Rolle, die diesen Blog verwaltet, in roles/blog_wordpress/tasks/main.yml: der Task „Set custom CSS“ für das Handy-Layout und „Deploy performance hints mu-plugin“ für die Bildpriorität und die Freigabe von fetchpriority.




