Gzip vs. Brotli vs. Zstd zur Komprimierung von Web-Assets
Brotli, gzip oder zstd für Web-Assets: wann welches Verfahren passt, wie Komprimierungsstufen die Größe beeinflussen und wie statische Dateien vorab komprimiert werden.
Verwenden Sie Brotli für statische Text-Assets, gzip als universellen Fallback und zstd für dynamische Responses, bei denen Sie sowohl den Server als auch das CDN kontrollieren.
Zahlreiche Bundles werden noch immer gzip-komprimiert ausgeliefert, weil ein Build-Tool oder eine Hosting-Voreinstellung diese Wahl vor Jahren getroffen hat und seither niemand mehr den Algorithmus oder die Stufe überprüft hat. Der weitere Verlauf dieses Artikels behandelt, warum statische und dynamische Inhalte unterschiedliche Problemstellungen darstellen, warum die Kompressionsstufe das Ergebnis oft stärker beeinflusst als der Name des Algorithmus, wie Sie zur Build-Zeit vorkomprimieren, wie Browser und Server verhandeln und was unkomprimiert bleiben sollte.
Die wichtigsten Erkenntnisse
- Brotli auf der höchsten Stufe ist die richtige Wahl für statisches JavaScript und CSS, weil die Kosten langsamer Kompression einmalig zur Build-Zeit anfallen und nicht pro Request.
- Zstd eignet sich für dynamische Responses: Cloudflare hat gemessen, dass es 42 % schneller komprimiert als Brotli und dabei größenmäßig nahe herankommt – mit Durchschnittswerten von 2,56:1 für gzip, 2,86:1 für zstd und 3,08:1 für Brotli.
- Der Stufenregler ist ebenso wichtig wie der Name des Algorithmus: Wer auf Brotli umstellt, aber eine schnelle Standardstufe beibehält, verschenkt einen Teil der Einsparung.
- Der
gzip_comp_levelvon nginx ist standardmäßig auf 1 gesetzt undbrotli_comp_levelvon ngx_brotli auf 6; keiner dieser Standardwerte entspricht dem Maximum. - Die Aushandlung der Kodierung findet vollständig über die Header
Accept-EncodingundContent-Encodingstatt – ein Algorithmuswechsel ist also eine Server- oder CDN-Einstellung und keine Änderung am Anwendungscode.
Brotli vs. Gzip vs. Zstd: Die Empfehlung
Brotli ist der richtige Standard für statisches JavaScript und CSS, weil die Kompression einmalig zur Build-Zeit erfolgt – die langsamen Höchststufen kosten zur Request-Zeit also nichts.
Gzip bleibt als Fallback im Stack, weil jeder Browser es in Accept-Encoding aufführt; es ist für nichts die beste Wahl, aber die einzige Wahl, die niemals fehlschlägt.
Zstd gehört auf dynamische Responses, bei denen die Kompressionskosten bei jedem Request anfallen, denn es erreicht ein Verhältnis nahe an Brotli – bei einem Bruchteil der CPU-Zeit.
Statisch vs. dynamisch: Wann fallen die Kompressionskosten an?
Die Entscheidung zwischen den Algorithmen ist eine Entscheidung darüber, wann die CPU-Kosten anfallen. Ein statisches Bundle wird einmal pro Release komprimiert und tausendfach ausgeliefert – nur das Verhältnis zählt, die Kompressionsgeschwindigkeit ist irrelevant. Eine pro Request gerenderte HTML-Seite wird bei jedem Aufruf komprimiert, sodass die Kompressionsgeschwindigkeit zu einem Latenz- und Serverkostenfaktor wird, der dem Verhältnis gleichrangig ist.
Cloudflares Ankündigung der Zstandard-Unterstützung vom September 2024 liefert die Zahlen für den dynamischen Fall. In einem Test im dritten Quartal 2024, bei dem der Traffic des Free-Plans 24 Stunden lang von Brotli auf zstd umgestellt wurde – mit zstd auf der Standardstufe 3 und ohne Angabe der Brotli- und gzip-Stufen –, komprimierte zstd 42 % schneller als Brotli und kam größenmäßig nahe heran. Die gemessenen Durchschnittswerte lagen bei 2,56:1 für gzip, 2,86:1 für zstd und 3,08:1 für Brotli, und Cloudflares eigenes Fazit lautete, dass zstd die bessere Wahl für dynamische Responses einschließlich HTML ist.
Fazit: Brotli gewinnt dort, wo das Verhältnis das einzige Kriterium ist – das trifft auf jedes statische Asset zu. Zstd gewinnt dort, wo die Kompression pro Request läuft.
Die Kompressionsstufe ist ebenso wichtig wie der Algorithmus
Jeder Algorithmus bietet einen Stufenregler, und die Stufe verändert das Ergebnis oft stärker als ein Algorithmuswechsel. Brotli nutzt in ngx_brotli die Stufen 0 bis 11, gzip in nginx die Stufen 1 bis 9, und das zstd-CLI die Stufen 1 bis 19 mit einem Standardwert von 3, zuzüglich 20 bis 22 hinter --ultra.
Wie stark der Regler das Ergebnis verändert, hängt vollständig vom Input ab. Veröffentlichte Vorher-Nachher-Werte für fremde Bundles sagen daher sehr wenig über Ihre eigenen aus. Schicken Sie Ihre Dateien durch das Compression-Comparison-Tool, das im Browser mit gzip, Brotli und zstd auf beliebiger Stufe komprimiert, und vergleichen Sie die Größen selbst.
Die Standardwerte erklären, warum so viele Websites am schnellen Ende ausliefern. Der gzip_comp_level von nginx ist standardmäßig auf 1 gesetzt, die schnellste der Stufen 1 bis 9. Der brotli_comp_level von ngx_brotli steht standardmäßig auf 6 von 0 bis 11. Keiner der Werte ist das Maximum, und beide sind für die Kompression im laufenden Betrieb sinnvoll – also genau der falsche Kontext für statische Assets. Welche Stufe Sie auch wählen: Bezahlt wird sie auf der komprimierenden Seite. Die README des zstd-Projekts weist selbst darauf hin, dass das Dekodieren etwa gleich schnell läuft, unabhängig davon, welche Stufe die Datei erzeugt hat – das gilt auch für zlib und lzma.
Fazit: Setzen Sie die Stufe bewusst. Maximum für alles Vorkomprimierte; eine mittlere Stufe für alles, was pro Request komprimiert wird.
Wie komprimiert man Assets zur Build-Zeit vor?
Vorkomprimierung bedeutet, dass der Build-Schritt neben jedem Asset ein .br-, .gz- und optional .zst-Pendant ablegt und der Server die passende Datei auswählt, ohne zur Request-Zeit irgendetwas zu komprimieren.
brotli -q 11 app.js # writes app.js.br; source kept by default
gzip -9 -k app.js # writes app.js.gz; -k keeps the source
zstd -19 app.js # writes app.js.zst; source kept by default
Das brotli-CLI behält Eingabedateien standardmäßig bei und akzeptiert -q für Qualitätsstufen von 0 bis 11. GNU gzip benötigt -k bzw. --keep, um das Original zu erhalten.
Für die Auslieferung der Pendants in nginx genügen zwei Direktiven:
load_module modules/ngx_http_brotli_static_module.so;
http {
gzip_static on; # serves .gz siblings; module needs --with-http_gzip_static_module
brotli_static on; # serves .br siblings; default off
gzip_vary on; # adds Vary: Accept-Encoding; default off
}
ngx_http_gzip_static_module wird nicht standardmäßig mitkompiliert. brotli_static stammt aus ngx_brotli. nginx liefert kein eigenes zstd-Modul aus; das Drittanbieter-Modul zstd-nginx-module ergänzt eine zstd_static-Direktive für .zst-Pendants, die standardmäßig deaktiviert ist. Ohne dieses Modul ist zstd für statische Dateien eine reine CDN-seitige Einstellung.
CDNs leiten vorkomprimierte Dateien durch. Cloudflares Dokumentation zur Kompression hält fest, dass ein content-encoding: br oder gzip des Origins beibehalten wird, sofern der Browser des Besuchers es unterstützt und keine Response-umschreibenden Funktionen (Rocket Loader, Email Address Obfuscation, Polish und andere) aktiviert sind; die Anfrage an Origins erfolgt mit accept-encoding: br, gzip, sodass zstd vom Origin nicht durchgereicht wird. Akamais Brotli-Support-Verhalten liefert vom Origin komprimiertes Brotli aus und cacht es, gibt Nicht-Brotli-Varianten an Clients zurück, die br nicht akzeptieren, und führt selbst keine Kompression am Edge durch.
Fazit: Komprimieren Sie zur Build-Zeit auf maximaler Stufe und konfigurieren Sie Server oder CDN so, dass das Pendant ausgeliefert wird.
Wie einigen sich Browser und Server auf eine Kodierung?
Der Browser listet die Kodierungen, die er dekodieren kann, in Accept-Encoding auf, der Server oder das CDN wählt eine aus und kennzeichnet die Response mit Content-Encoding – Anwendungscode ist an diesem Austausch nicht beteiligt.
GET /app.3f2a1b.js HTTP/1.1
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.1 200 OK
Content-Encoding: br
Vary: Accept-Encoding
br ist Brotlis Token in der HTTP-Content-Coding-Registry, definiert in RFC 7932, Abschnitt 13. Vary: Accept-Encoding weist gemeinsam genutzte Caches an, keinen Brotli-Body an einen Client auszuliefern, der nur gzip angefordert hat; nginx lässt den Header weg, sofern nicht gzip_vary on gesetzt ist.
Um zu sehen, was eine Website aktuell ausliefert, fordern Sie eine Bundle-Datei an und lesen den einen zurückgelieferten Header:
curl -sI -H 'Accept-Encoding: gzip, br, zstd' https://example.com/app.js | grep -i content-encoding
Dieselbe Information findet sich im Network-Panel der DevTools unter den Response-Headern.
Die Browser-Unterstützung bestimmt die Fallback-Reihenfolge. Brotli wird von allen aktuellen großen Browsern unterstützt. caniuse führt zstd in Chrome und Edge ab Version 123, Firefox ab 126, Opera ab 109 und Safari ab 26, wobei Desktop-Safari als teilweise unterstützt markiert ist. Jeder Browser, der zstd in Accept-Encoding weglässt, erhält schlicht Brotli oder gzip. Es gibt keinen Fehlerfall, nur einen Fallback.
Fazit: Ein Algorithmuswechsel ist eine Konfigurationsänderung, und die Fallback-Kette macht ihn sicher.
Was sollte man nicht komprimieren?
JPEG, MP4 oder WOFF2 erneut zu komprimieren, verschwendet CPU auf beiden Seiten für eine Einsparung von praktisch null, denn diese Formate sind intern bereits komprimiert. WOFF2 ist der eindeutigste Fall: RFC 7932, Abschnitt 1.2 hält fest, dass das dort definierte Format in WOFF 2.0 eingebaut ist – eine .woff2-Datei ist also bereits Brotli-Output.
Sehr kleine Responses sind der zweite Ausschlussfall. Unterhalb weniger Dutzend Bytes übersteigt der Kodierungs-Overhead die Einsparung. Deshalb stehen gzip_min_length von nginx und brotli_min_length von ngx_brotli standardmäßig auf 20 Bytes, und Cloudflare komprimiert nur Responses ab 48 Bytes für gzip bzw. 50 Bytes für Brotli und zstd.
Die Schlussfolgerungen hier gelten für die Dateien, die Browser tatsächlich empfangen – von wenigen Kilobyte bis zu einigen Megabyte. Benchmarks, in denen Brotli Minuten braucht, stammen von mehrere hundert Megabyte großen Dateien, die niemals über Content-Encoding übertragen werden.
Fazit: Komprimieren Sie Text (HTML, CSS, JavaScript, JSON, SVG); lassen Sie Medien, Schriftarten und winzige Responses aus.
Fazit
Die Algorithmusfrage ist geklärt: Brotli auf maximaler Stufe für alles, was zur Build-Zeit komprimiert wird, zstd für alles, was pro Request komprimiert wird, gzip als Basis, die jeder Client versteht. Die Frage nach der Stufe ist diejenige, die die meisten Stacks nie gestellt haben. Führen Sie den curl-Check gegen Ihr eigenes Bundle aus, notieren Sie die Kodierung und leiten Sie die Stufe aus der Größe ab – und falls die Antwort gzip auf einer schnellen Standardstufe lautet, sind Sie von der Vorkomprimierung mit Brotli nur einen Build-Schritt und zwei Server-Direktiven entfernt.
FAQs
Warum sendet meine Website weiterhin gzip, obwohl der Browser Brotli unterstützt?
Drei Ursachen erklären nahezu jeden Fall. Erstens geben Chrome und Firefox 'br' in Accept-Encoding nur über HTTPS an, sodass reine HTTP-Requests – einschließlich der meisten localhost-Entwicklung – auf gzip zurückfallen. Zweitens ist auf dem Server kein Brotli-Modul geladen; nginx benötigt ngx_brotli, das nicht eingebaut ist. Drittens reicht ein CDN wie Cloudflare eine Origin-Response durch, die bereits mit content-encoding: gzip gekennzeichnet ist, statt sie nach Brotli umzukodieren.
Was ist der Unterschied zwischen 'deflate' und 'gzip' in Content-Encoding?
Beide transportieren denselben DEFLATE-Algorithmus aus RFC 1951 und unterscheiden sich nur im Container. In HTTP bedeutet 'deflate' das zlib-Format aus RFC 1950 (Zwei-Byte-Header, Adler-32-Prüfsumme) und 'gzip' den RFC-1952-Container mit CRC-32-Trailer. Frühe Server und Browser sendeten unter dem Namen 'deflate' mitunter rohes DEFLATE, was Clients zum Raten zwang. Deshalb wurde gzip zur verlässlichen Wahl, und deflate lohnt sich nur selten.
Unterstützt Node.js Brotli- und zstd-Kompression nativ?
Ja. Das eingebaute Modul node:zlib implementiert die Content-Encodings gzip, deflate, br und zstd ohne Drittanbieter-Pakete. Brotli wird seit Node.js 11.7.0 über zlib.brotliCompress und zlib.createBrotliCompress ausgeliefert; Zstandard kam mit Node.js 23.8.0 über zlib.zstdCompress und zlib.createZstdCompress hinzu, und Node.js 24.6.0 ergänzte die zstd-APIs um Dictionary-Unterstützung (https://nodejs.org/en/blog/release/v24.6.0). Kompressions-Middleware, die älter als diese Releases ist, verhandelt möglicherweise weiterhin nur gzip – prüfen Sie daher das ausgegebene content-encoding.