{"id":15549,"date":"2026-09-05T13:30:55","date_gmt":"2026-09-05T11:30:55","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/"},"modified":"2026-09-05T13:30:55","modified_gmt":"2026-09-05T11:30:55","slug":"compression-negotiation-checker-gzip-brotli-zstd","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/","title":{"rendered":"Compression Negotiation Checker: gzip, Brotli, zstd"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">A browser announces what it can decode in Accept-Encoding, and the server picks one. Which one, and how much it saves, is invisible in the page itself. This check fetches the same address five times from this server, once uncompressed as the reference, once each asking only for gzip, Brotli and zstd, and once with the combined header current browsers send. For every answer it counts the bytes on the wire against the unpacked size and reads Content-Encoding, Vary and ETag.<\/p>\n<div style=\"margin-bottom: 14px;\">\n        <label for=\"cp-url\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Address (a page, a script, a stylesheet or an image)<\/label><br \/>\n        <input id=\"cp-url\" type=\"text\" value=\"https:\/\/www.wikipedia.org\/\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace;\">\n    <\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.82rem; margin-bottom: 16px;\">Redirects and five full downloads of up to 3 MB each go from this server to the address entered. Hosts in private, loopback and link-local networks are refused. The query is protected by reCAPTCHA v3; the calling IP address and the target are stored for one hour to limit the rate.<\/p>\n<p>    <button id=\"cp-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Check compression<\/button><\/p>\n<div id=\"cp-ausgabe\" style=\"margin-top: 22px;\"><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.8rem; margin: 24px 0 0;\">Limits worth knowing: only one of the resolved addresses is tested; a CDN may compress differently per edge or only after a first miss; the times are single measurements and mostly reflect transfer size and distance; bodies above 3 MB are cut off, so their ratios are approximate; and a server that compresses on the fly at request time may show slightly different byte counts on each run.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"A browser announces what it can decode in Accept-Encoding, and the server picks one. Which one, and how much it saves, is invisible in the page itself. This check fetches the same address five times from this server, once uncompressed as the reference, once each asking only for gzip, Brotli and zstd, and once with the combined header current browsers send. For every answer it counts the bytes on the wire against the unpacked size and reads Content-Encoding, Vary and ETag.\",\"label_url\":\"Address (a page, a script, a stylesheet or an image)\",\"datenschutz\":\"Redirects and five full downloads of up to 3 MB each go from this server to the address entered. Hosts in private, loopback and link-local networks are refused. The query is protected by reCAPTCHA v3; the calling IP address and the target are stored for one hour to limit the rate.\",\"knopf\":\"Check compression\",\"laeuft\":\"Running the five downloads...\",\"grenze\":\"Limits worth knowing: only one of the resolved addresses is tested; a CDN may compress differently per edge or only after a first miss; the times are single measurements and mostly reflect transfer size and distance; bodies above 3 MB are cut off, so their ratios are approximate; and a server that compresses on the fly at request time may show slightly different byte counts on each run.\",\"h_antwort\":\"Response\",\"zeile_url\":\"Measured address\",\"zeile_ip\":\"Resolved addresses\",\"zeile_server\":\"Server header\",\"zeile_typ\":\"Content type\",\"zeile_basis\":\"Unpacked size\",\"zeile_dauer\":\"Took\",\"zeile_limit\":\"Checks left this hour\",\"klasse_text\":\"compressible\",\"klasse_schon_komprimiert\":\"already compressed by format\",\"klasse_unbekannt\":\"unknown\",\"h_varianten\":\"Five requests, one address\",\"spalte_accept\":\"Accept-Encoding sent\",\"spalte_kodierung\":\"Content-Encoding\",\"spalte_leitung\":\"On the wire\",\"spalte_anteil\":\"Of unpacked\",\"spalte_status\":\"Status\",\"spalte_ttfb\":\"First byte\",\"spalte_gesamt\":\"Total\",\"variante_identity\":\"identity\",\"variante_gzip\":\"gzip\",\"variante_br\":\"br\",\"variante_zstd\":\"zstd\",\"variante_browser\":\"gzip, deflate, br, zstd\",\"gekappt_kurz\":\"(cut at 3 MB)\",\"fehlgeschlagen\":\"failed\",\"h_befunde\":\"Findings\",\"stufe_hoch\":\"High\",\"stufe_mittel\":\"Medium\",\"stufe_niedrig\":\"Low\",\"stufe_info\":\"Information\",\"stufe_gut\":\"In order\",\"keine_befunde\":\"Nothing to report.\",\"urteil_gut\":\"Compression is negotiated the way browsers expect.\",\"urteil_warnung\":\"Compression works, with something that costs bytes or confuses caches.\",\"urteil_kritisch\":\"No compression at all, or the address could not be fetched.\",\"fehler_kein_token\":\"The check could not be started because reCAPTCHA did not load.\",\"fehler_captcha\":\"reCAPTCHA classified the request as automated. Reloading the page normally helps.\",\"fehler_zu_viele\":\"The limit of {limit} checks per hour for this address has been reached.\",\"fehler_adresse\":\"That address cannot be used.\",\"fehler_zu_viele_spruenge\":\"The address redirects more than five times; the chain was not followed further.\",\"fehler_zaehler\":\"The rate counter is unavailable, so nothing was checked.\",\"fehler_pruefdienst\":\"The reCAPTCHA service could not be reached.\",\"fehler_aufbau\":\"The service is not configured correctly. The fault is on this side.\",\"fehler_eingabe\":\"The request could not be read.\",\"fehler_methode\":\"Wrong request method.\",\"fehler_netz\":\"The service could not be reached.\",\"fehler_antwort\":\"The answer could not be read.\",\"fehler_unbekannt\":\"Something went wrong that has no message of its own.\",\"grund_leer\":\"Nothing was entered.\",\"grund_zu_lang\":\"The input is too long.\",\"grund_unlesbar\":\"It does not parse as an address.\",\"grund_schema\":\"Only http and https addresses are checked.\",\"grund_benutzerinfo\":\"Inputs with credentials in them are refused.\",\"grund_kein_punkt\":\"The host name needs at least one dot.\",\"grund_hostform\":\"Enter a host name, not an IP address.\",\"grund_port\":\"Only the ports 80, 443, 8080 and 8443 are checked.\",\"grund_gesperrter_bereich\":\"That host resolves into a private or reserved network. Those are never contacted.\",\"grund_kein_dns\":\"The host name does not resolve.\",\"grund_abruf\":\"The address did not answer.\",\"befund_weiterleitung\":\"Redirected {n} time(s) to {ziel} || The measurements refer to the final address of the chain.\",\"befund_abruf_fehlgeschlagen\":\"The address does not answer || None of the five requests produced a response: {fehler}.\",\"befund_status_fehler\":\"The server answered with status {status} || The compression of an error page is measured, not of the intended resource.\",\"befund_gekappt\":\"Body cut at {mb} MB || The uncompressed reference is larger than the limit, so the ratios are relative to the first {mb} MB and only approximate.\",\"befund_kompression_erzwungen\":\"Compressed even when identity was requested ({kodierung}) || The server ignores Accept-Encoding and sends {kodierung} regardless. Clients that cannot decode it, including some monitoring tools and old libraries, get unreadable bytes.\",\"befund_inhalt_verschieden\":\"The five responses differ in content ({n} versions) || After unpacking, the bodies are not identical. Dynamic content, per-request tokens or A\\\/B variants change between requests, so the byte counts compare slightly different pages.\",\"befund_typ_schon_komprimiert\":\"Content type {typ} is compressed by its format || Images, video, fonts in WOFF2 and archives are already compressed; gzip or Brotli on top saves nothing and costs CPU. The absence of Content-Encoding here is correct.\",\"befund_unnoetig_komprimiert\":\"{kodierung} applied to an already compressed format ({prozent} % of the original) || The server compresses a resource whose format is already compressed. The saving is nil, and every response costs CPU time on the server and in the browser. Excluding these content types from the compression rule is the usual fix.\",\"befund_typ_unbekannt\":\"Content type {typ} not classified || Whether compression makes sense for this type is not known to this check; the byte counts still stand.\",\"befund_kleine_antwort\":\"Small response: {bytes} bytes || Below about 1 KB, compression barely pays off; the headers weigh more than the saving. Many servers skip it on purpose for small bodies.\",\"befund_groesse\":\"Unpacked size {kb} KB ({bytes} bytes) || The reference every ratio below refers to.\",\"befund_gzip_ok\":\"gzip: {prozent} % of the unpacked size ({kb} KB) || Every client supports gzip. This is the baseline any compressed site should reach.\",\"befund_gzip_andere\":\"Asked for gzip, got {kodierung} || The server answered with an encoding that was not offered. Clients that only asked for gzip cannot decode it.\",\"befund_gzip_fehlt\":\"No gzip for {kb} KB of compressible content || A request asking only for gzip received an uncompressed body. gzip is supported by every browser and every proxy since the nineties; leaving it out costs the whole saving for the majority of clients.\",\"befund_br_ok\":\"Brotli: {prozent} % of the unpacked size ({kb} KB) || Brotli is supported by all current browsers over https and typically saves 10 to 20 % over gzip on text.\",\"befund_br_groesser_als_gzip\":\"Brotli larger than gzip by {prozent} % || Brotli at a low quality level, or applied on the fly with a small window, can lose against a well-tuned gzip. Static precompression at quality 11 is where Brotli shines.\",\"befund_br_gegen_gzip\":\"Brotli saves {prozent} % against gzip || The difference between the two compressed sizes for this resource.\",\"befund_br_fehlt\":\"No Brotli || A request asking only for Brotli received an uncompressed body. Every current browser sends br in Accept-Encoding over https; enabling it (mod_brotli, ngx_brotli, or the CDN switch) typically cuts text transfers by another 10 to 20 % compared to gzip.\",\"befund_br_ersetzt\":\"Asked for Brotli, got {kodierung} || The server does not offer Brotli and answered with another encoding that was not requested. Strict clients refuse that.\",\"befund_zstd_ok\":\"zstd: {prozent} % of the unpacked size ({kb} KB) || Zstandard compresses about as well as Brotli and decodes faster. Chrome and Firefox send zstd in Accept-Encoding since 2024; Safari does not.\",\"befund_zstd_fehlt\":\"No zstd || Optional today: Chrome and Firefox accept it, Safari does not, and the gain over Brotli is small for typical pages. Worth it mainly for very large or frequently changing responses.\",\"befund_zstd_ersetzt\":\"Asked for zstd, got {kodierung} || The server does not offer zstd and answered with another encoding that was not requested.\",\"befund_browser_wahl_keine\":\"A browser gets no compression although {bestes} is available || With the combined header current browsers send, the server returned an uncompressed body, even though it compressed when a single encoding was requested. The negotiation logic prefers identity in the presence of several offers; that is a configuration error.\",\"befund_browser_wahl_keine_moeglich\":\"A browser gets no compression || The combined header did not produce a compressed body, consistent with the single requests.\",\"befund_browser_wahl_schlechter\":\"A browser gets {wahl} although {bestes} would be {prozent} % smaller || With the combined header, the server picked {wahl}. It offers {bestes} too, but only when asked for it alone. In Apache this is the order of mod_deflate and mod_brotli; in nginx, brotli must be enabled alongside gzip; a CDN may need the setting per content type.\",\"befund_browser_wahl\":\"A browser gets {wahl} ({prozent} % of the unpacked size) || The combined header current browsers send resulted in the best encoding the server offers.\",\"befund_verhaeltnis_schwach\":\"Weak ratio: at best {prozent} % of the unpacked size || Text usually compresses to 20 to 35 %. A ratio this close to 100 % means the body is already compact, embedded binary data dominates, or the compression level is very low.\",\"befund_vary_stern\":\"Vary: * || A wildcard Vary makes the response uncacheable by shared caches. If it was meant to cover Accept-Encoding, naming that header is enough.\",\"befund_vary_ok\":\"Vary: {vary} || Caches keep one copy per encoding and will not hand a Brotli body to a client that asked for gzip.\",\"befund_vary_fehlt\":\"Vary lacks Accept-Encoding (found: {vary}) || Compressed responses without Vary: Accept-Encoding let a shared cache store one variant and serve it to every client, including one that cannot decode it. Browsers and CDNs are lenient today, but proxies and older caches are not.\",\"befund_etag_gleich\":\"Same ETag for all encodings ({etag}) || Conditional requests cannot tell the encodings apart. A client that cached the gzip variant may get a 304 for a Brotli request. Apache appends -gzip and -br to the ETag for that reason.\",\"befund_etag_je_kodierung\":\"ETag differs per encoding || Each encoding carries its own validator, so conditional requests stay unambiguous.\",\"befund_chunked\":\"Chunked transfer || The compressed body is streamed without a Content-Length; the server compresses on the fly. The byte count comes from the bytes received, not from a header.\",\"befund_content_length\":\"Content-Length {bytes} || The compressed size is known up front, typical for precompressed files or a CDN serving from cache.\",\"befund_keine_kompression\":\"No compression at all for {kb} KB of compressible content || None of the requests received a compressed body. For text this means three to five times more bytes than needed on every visit.\",\"befund_zeit_vergleich\":\"Total time: uncompressed {identity} ms, {kodierung} {kodiert} ms || Single measurements from a nearby server over a fast link; the difference grows with distance and on mobile networks, where the byte count is what matters.\"};<\/p>\n<p>    var ENDPUNKT = '\/lw-compcheck.php';\n    var SITEKEY = '6LcrPkEtAAAAAPo1QCOf-IIM2fCL0UfdJz4y2iSY';\n    var STUFEN = ['hoch', 'mittel', 'niedrig', 'info', 'gut'];\n    var VARIANTEN = ['identity', 'gzip', 'br', 'zstd', 'browser'];\n    var KLASSEN = ['text', 'schon_komprimiert', 'unbekannt'];<\/p>\n<p>    function liste(w) { return Array.isArray(w) ? w : []; }\n    function zahl(w) { return (typeof w === 'number' && isFinite(w)) ? w : null; }\n    function text(schluessel, daten) {\n        var t = T[schluessel];\n        if (typeof t !== 'string') { return null; }\n        return t.replace(\/\\{([a-z_0-9]+)\\}\/g, function (m, k) {\n            return (daten && daten[k] !== undefined && daten[k] !== null) ? String(daten[k]) : m;\n        });\n    }\n    function kb(bytes) {\n        if (bytes === null) { return '-'; }\n        if (bytes < 1024) { return bytes + ' B'; }\n        if (bytes < 1048576) { return (Math.round(bytes \/ 102.4) \/ 10) + ' KB'; }\n        return (Math.round(bytes \/ 104857.6) \/ 10) + ' MB';\n    }\n\n    function abruf(r) {\n        r = (r &#038;&#038; typeof r === 'object') ? r : {};\n        return { accept: r.accept ? String(r.accept) : '', ok: !!r.ok, status: zahl(r.status), fehler: r.fehler ? String(r.fehler) : null,\n                 kodierung: r.kodierung ? String(r.kodierung) : null, leitung: zahl(r.leitung), entpackt: zahl(r.entpackt), prozent: zahl(r.prozent),\n                 contentLength: zahl(r.content_length), gekappt: !!r.gekappt, ttfb: zahl(r.ttfb_ms), gesamt: zahl(r.gesamt_ms) };\n    }\n\n    function bewerten(antwort) {\n        var a = antwort || {};\n        if (a.fehler) { return { fehler: String(a.fehler), grund: a.grund || null, limit: zahl(a.limit), kette: liste(a.kette) }; }\n        var befunde = liste(a.befunde).map(function (f) {\n            return { key: String(f.key || ''), stufe: STUFEN.indexOf(f.stufe) === -1 ? 'info' : f.stufe, daten: (f.daten &#038;&#038; typeof f.daten === 'object') ? f.daten : {} };\n        });\n        var gruppen = {};\n        STUFEN.forEach(function (s) { gruppen[s] = befunde.filter(function (f) { return f.stufe === s; }); });\n        var abrufe = {};\n        VARIANTEN.forEach(function (v) { abrufe[v] = abruf(a.abrufe ? a.abrufe[v] : null); });\n        return {\n            fehler: null, url: a.url ? String(a.url) : '', host: a.host ? String(a.host) : '', ip: a.ip ? String(a.ip) : '', ips: liste(a.ips), server: a.server ? String(a.server) : null,\n            urteil: (a.urteil === 'gut' || a.urteil === 'warnung' || a.urteil === 'kritisch') ? a.urteil : 'kritisch',\n            klasse: KLASSEN.indexOf(a.klasse) === -1 ? 'unbekannt' : a.klasse,\n            contentType: a.content_type ? String(a.content_type) : null, basis: zahl(a.basis) || 0,\n            abrufe: abrufe, befunde: befunde, gruppen: gruppen,\n            kette: liste(a.kette), dauer: zahl(a.dauer_ms), limitRest: zahl(a.limit_rest)\n        };\n    }\n\n    function skriptLaden() {\n        return new Promise(function (auf) {\n            if (window.grecaptcha) { auf(true); return; }\n            var s = document.createElement('script');\n            s.src = 'https:\/\/www.google.com\/recaptcha\/api.js?render=' + SITEKEY;\n            s.onload = function () { auf(true); }; s.onerror = function () { auf(false); };\n            document.head.appendChild(s);\n            setTimeout(function () { auf(!!window.grecaptcha); }, 8000);\n        });\n    }\n    function tokenHolen() {\n        return skriptLaden().then(function (da) {\n            if (!da || !window.grecaptcha || !window.grecaptcha.ready) { return null; }\n            return new Promise(function (auf) {\n                var fertig = false;\n                function einmal(w) { if (!fertig) { fertig = true; auf(w); } }\n                setTimeout(function () { einmal(null); }, 12000);\n                try {\n                    window.grecaptcha.ready(function () {\n                        if (fertig) { return; }\n                        if (!window.grecaptcha.execute) { einmal(null); return; }\n                        window.grecaptcha.execute(SITEKEY, { action: 'compcheck' }).then(function (t) { einmal(t || null); }, function () { einmal(null); });\n                    });\n                } catch (e) { einmal(null); }\n            });\n        }, function () { return null; });\n    }\n    function abfragen(url) {\n        return tokenHolen().then(function (token) {\n            return fetch(ENDPUNKT, { method: 'POST', headers: { 'Content-Type': 'application\/json' }, body: JSON.stringify({ url: url, token: token || '' }) })\n                .then(function (r) { return r.json().then(function (j) { return j; }, function () { return { fehler: 'antwort' }; }); }, function () { return { fehler: 'netz' }; });\n        });\n    }\n\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.CP = { bewerten: bewerten, darstellen: null, text: text, kb: kb, ENDPUNKT: ENDPUNKT, STUFEN: STUFEN, VARIANTEN: VARIANTEN, KLASSEN: KLASSEN, T: T };\n\n    var btn = document.getElementById('cp-btn');\n    var out = document.getElementById('cp-ausgabe');\n    if (!btn || !out) { return; }\n\n    function el(tag, stil, txt) { var e = document.createElement(tag); if (stil) { e.setAttribute('style', stil); } if (txt !== undefined &#038;&#038; txt !== null) { e.textContent = txt; } return e; }\n    function wert(id) { var e = document.getElementById(id); return e ? e.value : ''; }\n    var UEBERSCHRIFT = 'font-family: \"Nunito Sans\", sans-serif; font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 22px 0 8px;';\n    var ZELLE = 'padding: 5px 12px 5px 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem;';\n    var ZELLE_WERT = 'padding: 5px 12px 5px 0; color: var(--text-primary, #e8e8ee); font-size: 0.87rem; font-family: monospace; word-break: break-all;';\n    var KASTEN = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 8px; padding: 14px 16px; margin: 0 0 10px;';\n    var GUT = ' color: #7ee787;', WARN = ' color: #ffa94d;', ROT = ' color: #ff7b72;', GRAU = ' color: #8a8a99;';\n    var FARBEN = { hoch: '#ff7b72', mittel: '#ffa94d', niedrig: '#e3b341', info: '#8a8a99', gut: '#7ee787' };\n    var URTEIL = { gut: GUT, warnung: WARN, kritisch: ROT };\n    function gitter(sp) { return el('div', 'display: grid; grid-template-columns: ' + sp + '; gap: 0 18px; align-items: baseline;'); }\n    function paar(g, n, w, stil) { g.appendChild(el('div', ZELLE, n)); g.appendChild(el('div', ZELLE_WERT + (stil || ''), w)); }\n    function ms(v) { return v === null ? '-' : v + ' ms'; }\n\n    function balken(prozent) {\n        var h = el('div', 'height: 8px; background: var(--border, #2a2a35); border-radius: 4px; width: 140px; overflow: hidden; display: inline-block; vertical-align: middle; margin-right: 8px;');\n        var f = el('div', 'height: 100%; width: ' + Math.max(1, Math.min(100, prozent)) + '%; background: ' + (prozent < 40 ? '#7ee787' : (prozent < 80 ? '#e3b341' : '#ffa94d')) + ';');\n        h.appendChild(f);\n        return h;\n    }\n\n    function darstellen(b, ziel) {\n        ziel.innerHTML = '';\n        if (b.fehler) {\n            var txt = T['fehler_' + b.fehler] || T.fehler_unbekannt;\n            if (b.grund &#038;&#038; T['grund_' + b.grund]) { txt = txt + ' ' + T['grund_' + b.grund]; }\n            if (b.limit !== null) { txt = txt.replace('{limit}', String(b.limit)); }\n            ziel.appendChild(el('p', ZELLE + WARN + ' margin: 0;', txt));\n            return;\n        }\n        ziel.appendChild(el('div', 'font-size: 1.05rem; font-weight: 700; margin: 0 0 12px;' + URTEIL[b.urteil], T['urteil_' + b.urteil]));\n\n        ziel.appendChild(el('div', UEBERSCHRIFT, T.h_antwort));\n        var g = gitter('auto auto');\n        paar(g, T.zeile_url, b.url);\n        paar(g, T.zeile_ip, b.ips.length ? b.ips.join(', ') : b.ip, GRAU);\n        if (b.server) { paar(g, T.zeile_server, b.server, GRAU); }\n        paar(g, T.zeile_typ, (b.contentType || '-') + ' (' + T['klasse_' + b.klasse] + ')', b.klasse === 'text' ? '' : GRAU);\n        paar(g, T.zeile_basis, kb(b.basis) + (b.basis ? ' (' + b.basis + ' B)' : ''));\n        if (b.dauer !== null) { paar(g, T.zeile_dauer, ms(b.dauer), GRAU); }\n        if (b.limitRest !== null) { paar(g, T.zeile_limit, String(b.limitRest), GRAU); }\n        ziel.appendChild(g);\n\n        ziel.appendChild(el('div', UEBERSCHRIFT, T.h_varianten));\n        var huelle = el('div', 'overflow-x: auto;');\n        var gp = gitter('auto auto auto auto auto auto auto');\n        [T.spalte_accept, T.spalte_kodierung, T.spalte_leitung, T.spalte_anteil, T.spalte_status, T.spalte_ttfb, T.spalte_gesamt].forEach(function (s) { gp.appendChild(el('div', ZELLE + ' font-weight: 700; color: var(--text-primary, #e8e8ee); white-space: nowrap;', s)); });\n        VARIANTEN.forEach(function (v) {\n            var r = b.abrufe[v];\n            gp.appendChild(el('div', ZELLE_WERT + ' white-space: nowrap;', r.accept || T['variante_' + v]));\n            if (r.ok) {\n                var kod = r.kodierung || 'identity';\n                gp.appendChild(el('div', ZELLE_WERT + (kod === 'identity' ? GRAU : GUT), kod));\n                gp.appendChild(el('div', ZELLE_WERT, kb(r.leitung) + (r.gekappt ? ' ' + T.gekappt_kurz : '')));\n                var z = el('div', ZELLE_WERT + ' white-space: nowrap;');\n                if (r.prozent !== null) { z.appendChild(balken(r.prozent)); z.appendChild(document.createTextNode(r.prozent + ' %')); } else { z.textContent = '-'; }\n                gp.appendChild(z);\n                gp.appendChild(el('div', ZELLE_WERT + GRAU, r.status === null ? '-' : String(r.status)));\n                gp.appendChild(el('div', ZELLE_WERT + GRAU, ms(r.ttfb)));\n                gp.appendChild(el('div', ZELLE_WERT + GRAU, ms(r.gesamt)));\n            } else {\n                gp.appendChild(el('div', ZELLE_WERT + ROT + ' grid-column: span 6; white-space: normal;', T.fehlgeschlagen + (r.fehler ? ' (' + r.fehler + ')' : '')));\n            }\n        });\n        huelle.appendChild(gp);\n        ziel.appendChild(huelle);\n\n        ziel.appendChild(el('div', UEBERSCHRIFT, T.h_befunde));\n        var irgendwas = false;\n        STUFEN.forEach(function (s) {\n            var gr = b.gruppen[s];\n            if (!gr.length) { return; }\n            irgendwas = true;\n            ziel.appendChild(el('div', 'font-weight: 700; font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.5px; margin: 14px 0 6px; color: ' + FARBEN[s] + ';', T['stufe_' + s] + ' (' + gr.length + ')'));\n            gr.forEach(function (f) {\n                var k = el('div', KASTEN + ' border-left: 3px solid ' + FARBEN[s] + ';');\n                var t = text('befund_' + f.key, f.daten) || f.key;\n                var teile = t.split(' || ');\n                k.appendChild(el('div', 'color: var(--text-primary, #e8e8ee); font-size: 0.9rem; font-weight: 700;', teile[0]));\n                if (teile[1]) { k.appendChild(el('div', ZELLE + ' padding: 6px 0 0; line-height: 1.55;', teile[1])); }\n                ziel.appendChild(k);\n            });\n        });\n        if (!irgendwas) { ziel.appendChild(el('p', ZELLE + ' margin: 0;', T.keine_befunde)); }\n    }\n    window.LW_TEST.CP.darstellen = darstellen;\n\n    var laeuft = false;\n    btn.addEventListener('click', function () {\n        if (laeuft) { return; }\n        laeuft = true; btn.disabled = true;\n        out.innerHTML = '';\n        out.appendChild(el('p', ZELLE + GRAU + ' margin: 0;', T.laeuft));\n        abfragen(wert('cp-url')).then(function (a) { darstellen(bewerten(a), out); laeuft = false; btn.disabled = false; },\n            function () { darstellen(bewerten({ fehler: 'netz' }), out); laeuft = false; btn.disabled = false; });\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fetches an address five times with different Accept-Encoding values and shows what the server actually sends: which encodings it offers, how many bytes cross the wire for each, what a browser&#8217;s combined header gets, and whether Vary and ETag keep caches from mixing them up.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[91144,91243,91315],"class_list":["post-15549","page","type-page","status-publish","hentry","tag-devops","tag-networking","tag-web-performance"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Compression Negotiation Checker: gzip, Brotli, zstd - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Compression Negotiation Checker: gzip, Brotli, zstd - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Fetches an address five times with different Accept-Encoding values and shows what the server actually sends: which encodings it offers, how many bytes cross the wire for each, what a browser&#039;s combined header gets, and whether Vary and ETag keep caches from mixing them up.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/compression-negotiation-checker-gzip-brotli-zstd\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/compression-negotiation-checker-gzip-brotli-zstd\\\/\",\"name\":\"Compression Negotiation Checker: gzip, Brotli, zstd - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-05T11:30:55+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/compression-negotiation-checker-gzip-brotli-zstd\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/compression-negotiation-checker-gzip-brotli-zstd\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/compression-negotiation-checker-gzip-brotli-zstd\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Compression Negotiation Checker: gzip, Brotli, zstd\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Compression Negotiation Checker: gzip, Brotli, zstd - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/","og_locale":"en_US","og_type":"article","og_title":"Compression Negotiation Checker: gzip, Brotli, zstd - Lukas Wojcik - Blog","og_description":"Fetches an address five times with different Accept-Encoding values and shows what the server actually sends: which encodings it offers, how many bytes cross the wire for each, what a browser's combined header gets, and whether Vary and ETag keep caches from mixing them up.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/","name":"Compression Negotiation Checker: gzip, Brotli, zstd - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-05T11:30:55+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/compression-negotiation-checker-gzip-brotli-zstd\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"Compression Negotiation Checker: gzip, Brotli, zstd"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/15549","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=15549"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/15549\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=15549"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=15549"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}