The measurement
One real document: 400 orders, an array of objects with a consistent shape, 2-space indentation. It was measured raw, minified, and then compressed with both gzip at its highest level and brotli at its default quality.
| Form | Size | Compared with |
|---|---|---|
| Indented, 2 spaces | 221,421 B | 216.2 KB — the baseline |
| Minified | 124,588 B | 43.7% smaller than the indented file |
| Indented, gzip -9 | 10,879 B | 10.6 KB |
| Minified, gzip -9 | 9,700 B | only 10.8% smaller than gzipping the indented file |
| Indented, brotli | 5,038 B | 4.9 KB |
| Minified, brotli | 4,791 B | only 4.9% smaller than brotli-compressing the indented file |
Read the middle two rows together. Minification removes 96,833 bytes from the raw file — a 43.7% cut that looks excellent until you compress. After gzip the same minification is worth 1,179 bytes, and after brotli it is worth 247 bytes. The whitespace was never the expensive part.
Why the saving disappears
gzip and brotli are back-reference compressors: they look for repeated byte sequences and replace each repeat with a short pointer. Indentation is almost nothing but repeats — the same two spaces, in runs whose length is predictable from the nesting depth — so it costs a handful of pointers instead of 96 KB.
What compresses badly is entropy: unique keys, random IDs, timestamps and free-text values. Those are exactly the bytes minification leaves untouched. That is the whole explanation, and it generalises to almost any JSON payload.
The bigger lever is not minification
Compare the two ways of shrinking the wire size. Minifying on top of gzip saves 10.8%. Switching from gzip to brotli, with no change to the document at all, takes 10,879 bytes down to 5,038 — a 53.7% reduction. Enabling brotli is roughly five times the win, and it is a server configuration change rather than a build step.
The ordering that follows is: enable compression first, remove unused fields second, minify third. Dropping a field nobody reads beats every whitespace decision you will ever make.
Where minifying still wins
- When compression is off. A surprising number of internal services, misconfigured proxies and CDN rules serve JSON with no
Content-Encodingat all. There, the raw 43.7% is real. - Storage with size limits. Rows in a database, keys in Redis, entries in
localStorageand files on disk are often stored uncompressed, and often have quotas. - Message queues and sockets. Payload caps on queues and log pipelines are measured on the uncompressed body. WebSocket frames are not compressed unless
permessage-deflatewas negotiated. - Anything in a URL. A document encoded into a query string or a share link is transferred uncompressed, and every byte counts against a length limit.
- Signing and hashing. Here minification is about determinism, not size: two serialisations of the same object differ in whitespace, so sign one canonical form and verify that same form.
How to check your own traffic
Open the network panel, click the JSON request, and look at the Size column: it shows the transferred size, and the response headers show whether content-encoding is gzip or br. From a terminal, ask for both and read the header:
curl -sI -H 'Accept-Encoding: gzip, br' https://example.com/data.json \
| grep -iE 'content-encoding|content-length'If there is no content-encoding line, the response is going out uncompressed — fix that before spending any time on the minifier. If it says br, you are already at the best point on this chart.
To see the raw numbers for your own document, the JSON Minifier reports character and byte counts before and after, without sending anything anywhere.