Stand: Juli 2026
Brotli ist das modernere der beiden Kompressionsverfahren, die heute jeder Browser spricht: Es packt Text-Ressourcen (HTML, CSS, JavaScript) typischerweise 15–25 % kleiner als das altgediente gzip. Kleinere Übertragung heißt schnellerer Seitenaufbau — direkt messbar in den Core Web Vitals, besonders auf Mobilverbindungen. Kurz: Brotli will man haben.
Der Haken für die Shopware-Welt: Wer Varnish als HTTP-Cache einsetzt — und das ist in leistungsorientierten Setups der Standard —, bekommt Brotli nicht geschenkt. Varnish spricht von Haus aus kein Brotli; es kann intern nur mit gzip umgehen. Wer einfach „Brotli aktiviert“, riskiert wahlweise gar keine Kompression, doppelte Arbeit pro Anfrage oder einen Cache, der sich in Dutzende Varianten zersplittert. Es gibt drei saubere Wege — und eine Falle.
Was Brotli wirklich bringt — gemessen
Damit das keine Katalogzahl bleibt, haben wir ein echtes Shopware-6-Storefront-Asset genommen (die kompilierte Theme-CSS, 366 KB) und durchkomprimiert:
- Unkomprimiert: 366 KB
- gzip −9: 56,5 KB (−84,6 %)
- Brotli −q 5 (typisches On-the-fly-Niveau): 51,8 KB — 8 % kleiner als gzip
- Brotli −q 11 (vorkomprimiert beim Build): 43,4 KB — 23 % kleiner als gzip
Die Lehre daraus: Den großen Sprung macht Kompression überhaupt (−85 %). Brotli legt darauf noch einmal spürbar nach — am meisten, wenn man die höchste Stufe vorab rechnet statt bei jeder Anfrage.
Die drei sauberen Ketten
1. Statische Assets vorkomprimieren (der beste Einstieg)
CSS und JavaScript ändern sich nur beim Deployment — also komprimiert man sie einmal beim Build auf höchster Stufe und legt die .br-Dateien neben die Originale. Der Webserver liefert sie dann ohne jede Laufzeit-CPU aus:
# nginx mit ngx_brotli-Modul
brotli_static on; # liefert vorhandene .br-Dateien direkt aus
gzip_static on; # Fallback für Browser ohne Brotli
# beim Deployment, z. B. für das Shopware-Theme-Verzeichnis
find public/theme -type f \( -name '*.css' -o -name '*.js' \) \
-exec brotli -q 11 -f {} \;
Statische Dateien gehören ohnehin nicht durch Varnish. In den meisten Produktiv-Setups übernimmt sie ein CDN (CloudFront & Co.) — und dort muss man zwei Dinge wissen: Erstens komprimieren CDNs am Edge nur on-the-fly auf mittlerem Niveau (etwa −q-4/5) — den vollen q-11-Gewinn bekommt nur, wer am Origin vorkomprimiert und das CDN die fertigen Varianten durchreichen lässt. Zweitens muss das CDN die Varianten getrennt nach Accept-Encoding cachen (bei CloudFront: Cache-Policy mit gzip- und Brotli-Support), sonst bekommt der falsche Browser die falsche Datei. Zwischen On-the-fly-Brotli und vorkomprimiertem q 11 liegen noch einmal rund 15 % Ersparnis — auf der render-kritischen Theme-CSS direkt LCP-wirksam.
Ohne CDN gilt dasselbe Prinzip eine Etage tiefer: eine eigene, PHP-freie nginx-Instanz (bzw. ein eigener location-Block) nur für statische Dateien, die an Varnish vorbei direkt ausliefert — mit brotli_static und den vorkomprimierten Dateien. Null Laufzeit-Risiko, voller Gewinn.
2. Brotli vor Varnish (im TLS-Terminator)
Für das gecachte HTML selbst: Der nginx, der vor Varnish TLS terminiert, komprimiert die Antworten auf dem Weg nach draußen. Varnish cached unkomprimiert (oder gzip), die Brotli-Arbeit passiert pro Anfrage im Frontend-Proxy:
brotli on;
brotli_comp_level 5; # on-the-fly: 4–6 ist der sinnvolle Bereich
brotli_types text/html text/css application/javascript
application/json image/svg+xml;
Einfach und robust — kostet aber CPU pro Auslieferung. Auf Stufe 5 ist das im einstelligen Millisekundenbereich und für die meisten Shops verschmerzbar.
3. Brotli hinter Varnish (Varianten cachen)
Das Backend komprimiert, Varnish speichert je eine Variante pro Encoding. Funktioniert nur mit strikter Disziplin — hier lauert die Falle:
Die Vary-Falle: Antwortet das Backend mit Vary: Accept-Encoding, legt Varnish für jede vorkommende Accept-Encoding-Zeichenkette eine eigene Cache-Kopie an. Browser schicken aber Dutzende Spielarten (gzip, deflate, br, br;q=1.0, gzip;q=0.8, …) — der Cache zersplittert, die Trefferrate bricht ein. Deshalb wird der Header in vcl_recv auf genau eine von drei Stufen normalisiert:
sub vcl_recv {
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "br") {
set req.http.Accept-Encoding = "br";
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} else {
unset req.http.Accept-Encoding;
}
}
}
Damit existieren maximal drei Varianten pro Objekt. Preis dieser Kette: mehr Cache-Belegung, mehr Konfigurationsfläche — und Varnish kann die br-Variante nicht selbst entpacken, falls doch ein Client ohne Brotli kommt. Ein Praxis-Detail, das gern vergessen wird: Beim gezielten Invalidieren (PURGE/Ban) müssen alle Encoding-Varianten erwischt werden — wer nur eine purgt, liefert der anderen Browser-Hälfte munter den alten Stand weiter. In produktiven VCLs sieht man dafür Doppel-Pass-Konstruktionen, die den Purge einmal je Variante ausführen.
Einordnung
Unsere Empfehlung (als solche gemeint): erst Kette 1 — vorkomprimierte Assets sind der größte Gewinn zum kleinsten Preis. Danach Kette 2 für das HTML, wenn die Vitals es rechtfertigen. Kette 3 nur, wenn Frontend-CPU wirklich knapp ist und jemand die VCL-Disziplin dauerhaft pflegt.
Zum selben Themenkreis: Varnish Cache-Warming — dieselbe Varianten-Logik entscheidet, was man wärmt. Warum uns Auslieferungs-Performance so beschäftigt, zeigt das Web-Vitals-Panel auf der Shopware-Seite — Felddaten, live.