Site performance

Compression that only looks like it works: 170 KB instead of 76

The server supports brotli, everything is enabled in the settings, and the check reports «compression is on». Meanwhile the user's browser receives a page twice as heavy, every single time. A breakdown of our own case with before and after measurements.

September 2, 2026
7 min read

In an earlier breakdown about site speed, one of the checks was: measure the weight of the page and make sure compression works. The check is right, the test is simple — and it is exactly where we caught ourselves out. Our site passed it. Compression worked. Just not the one that mattered, and not for the people who mattered.

This article is a breakdown of our own case with two figures: 169.7 KB before and 76.2 KB after, with the page content unchanged.

The check that lies

The usual way to confirm compression is enabled looks like this:

curl -s -H "Accept-Encoding: br" -o /dev/null \
  -w "%{size_download} bytes\n" https://your-site.com/

A small number comes back and you are satisfied. The problem is the header you have just sent. You asked for brotli only — so of course the server gave you brotli.

A real Chrome asks for something else:

Accept-Encoding: gzip, deflate, br, zstd

That is a list of what the browser can handle, not a demand. The server picks. And on 21 August 2026 our server answered that header with gzip at 169.7 KB, while brotli of the very same page weighed 76 KB. The better option existed, sat right there, and reached no live visitor at all.

One rule follows: measure with the header a browser actually sends. A measurement with Accept-Encoding: br produces a pretty number that nobody receives.

curl -s -H "Accept-Encoding: gzip, deflate, br, zstd" -o /dev/null \
  -w "size: %{size_download} bytes\n" \
  -D - https://your-site.com/ | grep -i content-encoding

The last line shows what actually arrived: br, gzip, or nothing.

Two causes, both non-obvious

First: the application compressed it first. Many frameworks compress the response themselves, and do so by default, with no configuration at all. The response then reaches the proxy — which sees an already compressed stream and leaves it alone. That is correct behaviour: decompressing and recompressing someone else's work would be waste. The consequence is that your carefully configured brotli on the proxy is simply never called.

In our case the framework only knows gzip. So the chain ran: application compressed to gzip → proxy saw a finished job and passed it through → browser received gzip. Brotli never took part.

Second: that gzip was weaker than the fastest gzip. This is the strangest part of the measurement. We took the same page and compressed it locally:

Method Size
gzip -1 (fastest level) 143.6 KB
gzip -9 (strongest) 111.0 KB
what the server served 169.7 KB

The server lost to even the weakest level by 18%. The cause is not in the settings but in how a modern page is served: not as a finished document but as a stream, in chunks, as the server assembles it. Streaming compression never sees the whole document and cannot exploit repetition that an archiver would spot on a finished file. That is the price of a faster first byte.

What we did

We switched compression off inside the application entirely. Raw HTML now leaves the container — 788 KB, and that is fine: this stream never leaves the machine.

Compression is done by the proxy, and it is given an explicit order of encodings: brotli first, gzip second. The order has to be set by hand — the default queue in Traefik puts gzip first, which is precisely why it kept choosing the worse option with brotli sitting right there.

The result on the live site:

Client Before After
Chrome (asks for everything) 169.7 KB 76.2 KB
client without brotli 169.7 KB 120.8 KB

Minus 55% on every load of the home page, with no change to the content.

A trap that can cost you someone else's site. Container management panels often hand every application the same, shared-by-name compression rule. The namespace on the proxy is shared. That means «I'll just add brotli to the existing rule» changes behaviour not for one site but for everyone using that name — and two different definitions of one name produce non-deterministic results. The rule must be created under its own name and attached to its own route. There is no other safe way.

The other half: the weight is not in the markup

While hunting for the missing brotli we found something else. The question «what actually weighs anything on this page» has an unexpected answer for any modern frontend.

An analysis on 16 August 2026: in the compressed page, markup was 37 KB while serialized data for components was 107 KB. Three quarters of the weight is not what you see in the page source but what the framework ships to the browser to bring interactive blocks to life.

The same run measured something that usually frightens developers: 130 inline SVG icons cost 4 KB after compression. Compression handles repetition beautifully, so «lots of identical icons» is not a problem, however much intuition says otherwise.

And in the data sat something that had no business being there: complete translation objects. On the Ukrainian home page — 18 of them, each with English and Russian branches inside; on the portfolio page — 62. The cause was architectural: blocks applied the translation in the browser, so the server had to ship every language to every visitor.

We moved translation to the server. The result:

Page Before After
portfolio 216 KB 138 KB
home 201 KB 160 KB
English home 189 KB 151 KB

How to look at this on your own site

Three checks, a few minutes each.

1. What the browser actually receives. A Chrome-like header, then look at content-encoding and the size — the command at the top of this article. If gzip arrived while your proxy is configured for brotli, find out who compressed earlier.

2. Whether your server loses to a plain archiver.

curl -s https://your-site.com/ > page.html
gzip -9 -c page.html | wc -c

Compare with what the server served. If the server is heavier, compression is streaming or the level is set too low.

3. How much of the weight is data rather than markup. In DevTools, the Network tab, filter by Doc, look at the document size. Then in the page source look for the large data blocks the framework inserts for hydration. If they outweigh the visible markup several times over, that is what needs optimizing — not the images.

One specific thing worth searching for in that data: whether it carries everything that only some visitors need — all languages, full reference lists, internal database fields. That is the most common and cheapest find.

Honest limits

Minus 90 KB per load is noticeable on mobile internet and almost invisible on a wired connection. Compression does not cure a slow server: if your time to first byte is 800 milliseconds, page weight is not your main problem and you should start from the other end.

Nor does it affect rankings directly. The influence is indirect and honestly described in Google's documentation: speed is part of page experience signals, which work as one factor among many rather than as a lever. We fixed this not for search but because serving a page twice as heavy while having brotli configured is simply waste.

And the main conclusion, which transfers to any site: a check performed in the way that suits the server tells you about the server, not about the visitor. The header you measure with has to match the one the browser sends. Otherwise everything is excellent — right up until the first real user.

Tags

Performance

🤔Did you like the article?

Your opinion helps us create better content

Share with friends

Found something useful? 🚀

Help others learn about it - share the article on social networks

https://lionex.com.ua/blog/stysnennya-yake-nachebto-pracyuye

💚 Thank you for helping us grow

Vladyslav Chystiakov

Пише про те, що робить руками: інтернет-магазини на OpenCart, застосунки на Next.js, інтеграції та швидкість сайтів. У статтях — заміри й перевірки, які читач може повторити на своєму проєкті, а не загальні поради. Комерційна розробка з 2015 року.

Frequently asked questions

Answers to common questions on the topic

Only if a proxy or web server in front of it will compress instead. Otherwise you simply switch compression off. The check is easy: after the change, make a request with a browser-like header and look at content-encoding in the response.

Both. Brotli produces a smaller payload for the same content, gzip is understood by every client there is. A correct setup serves brotli to those who ask for it and gzip to everyone else. The mistake is usually not the choice but the order: the default queue on many proxies puts gzip first, so brotli is never reached.

Because it compresses a stream, chunk by chunk, as the page is assembled, while an archiver works on a finished file and sees every repetition at once. That is the price of a faster first byte. If the gap grows to one and a half times or more, it is worth checking who compresses and at which level.

On mobile networks and slow connections they do — every hundred kilobytes is a real fraction of a second. On a wired connection you will not notice. And compression does not cure a slow server: if your time to first byte is around a second, weight is not where to start.

Get the best articles by email

Subscribe to our newsletter and receive useful tips, insights and news about web development, marketing and business.

We respect your privacy. You can unsubscribe at any time.