HTTP/2, HTTP/3 and Minification: Is Minification Still Worth It?

HTTP/2, HTTP/3 and Minification: Is Minification Still Worth It?

HTTP/2 and HTTP/3 change how files travel, not how big they are. Measured sizes, compile times and bundling tests show what minification still saves.

22.10.2026
15 min read
Share this article:
HTTPMinificationCompressionPerformanceBundle

HTTP/2 changed how files travel, not how big they are

A claim keeps coming back: since HTTP/2 multiplexes requests, you no longer need to minify or bundle. Half of it is true. The protocol removed the cost of many small requests. It did not remove a single byte from a JavaScript file, and the browser still has to download, decode and compile every one of them. This guide separates what HTTP/2 and HTTP/3 changed from what they left alone. The figures come from three tests run on 22 October 2026 (the FastMinify benchmark dataset, a package of 644 modules and a V8 compile test). The RFCs and browser documentation cited along the way cover the protocol facts. For the compression side, see our GZIP and Brotli guide.

HTTP/2 and HTTP/3 change how requests are carried, not the size of the files inside them.
On 10 unminified JavaScript libraries, minification still removes about half of the gzip and brotli bytes.
On CSS the gain after compression falls to about 6%, so the case is weaker there.
644 tiny modules compress 2.4 times worse than the same code in one file.
V8 compiled the minified libraries 7% to 37% faster, which is 0.1 to 1.2 ms per library on a fast laptop.
Server push, domain sharding and one file for the whole site are the habits to retire.

What the protocols changed, and what they left alone

HTTP/2 carries many requests on one connection

HTTP/2 (RFC 9113, June 2022) carries many requests and responses at the same time on one connection. Under HTTP/1.1 a browser typically opened six connections per host and queued the rest. That queue is why teams concatenated files, drew image sprites and spread assets over several hostnames.

Improvements

Multiplexing lets a page request dozens of files without waiting for a free connection.
HPACK compresses repeated headers, so cookies and user agents are not sent in full on every request.
The old tree of stream priorities is deprecated. RFC 9218 replaces it with a Priority header (urgency from 0 to 7, plus an incremental flag).
RFC 9113 states that head-of-line blocking at the TCP level is not addressed. One lost packet still stalls every stream on the connection.
HTTP/3 keeps the semantics and swaps the transport

HTTP/3 (RFC 9114, June 2022) keeps the HTTP model but runs over QUIC instead of TCP. A lost packet now stalls only the stream it belongs to, where on HTTP/2 it stalls every transfer on the connection.

Improvements

Header compression moves from HPACK to QPACK, which is designed for streams that can arrive out of order.
The benefit depends on packet loss. We did not measure it here, so we make no claim about page speed.
Adoption is real but hard to measure. The 2024 Web Almanac counted 7% of desktop and 9% of mobile home pages loaded over HTTP/3 in its crawl. Its author puts real support closer to 30%.
In the same chapter, 71% of desktop and 70% of mobile home pages were served over HTTP/2, and 85% of requests used HTTP/2 or HTTP/3.
What neither protocol touches

Both protocols move bytes. Neither one changes how many bytes a file has, and neither one parses your code.

Improvements

Content-Encoding (gzip or brotli) works the same way on HTTP/1.1, HTTP/2 and HTTP/3.
After decompression the browser holds the full text of the script. It must parse and compile all of it, whatever protocol delivered it.
Server push, the feature meant to send files before they were requested, was switched off by default in Chrome 106. The Chrome team cited weak results (about 1.25% of HTTP/2 sites used it) and recommends 103 Early Hints instead.
103 Early Hints sends only hints and leaves the choice to the browser. The 2024 Web Almanac found it on 2.9% of desktop home pages.
What minification still saves after compression

We used the FastMinify minification benchmark (edition 2026-10). It measures unminified files from public libraries raw, with gzip level 9 and with brotli quality 11. Each figure is a sum over the files of one language. JavaScript uses Terser 5.51.2 at default settings, CSS uses CSSO and JSON uses JSON.stringify.

Improvements

JavaScript, 10 files: 5,637,983 bytes raw and 1,142,675 with gzip. Minified, 1,809,323 bytes raw and 566,618 with gzip, which is 67.9% and 50.4% smaller.
The same JavaScript with brotli: 899,755 bytes before and 484,012 after (46.2% smaller).
CSS, 6 files: 1,593,647 bytes raw and 1,338,588 minified (16.0% smaller). With gzip, 161,522 against 151,049 (6.5% smaller). With brotli, 106,426 against 99,630 (6.4% smaller).
JSON, 6 indented files: 14,199,411 bytes raw and 7,747,070 minified (45.4% smaller). With gzip the gain is 28.8%, with brotli 15.8%.
Read these as an upper bound. The corpus is unminified source with comments and indentation, and your own files will land lower if they are already lean.
644 modules or one file

We downloaded lodash-es 4.18.1, which ships 644 JavaScript modules, minified each module with esbuild and compressed with gzip level 9 and brotli quality 11. It is an extreme case, since few pages load 644 modules, but it shows the mechanism.

Improvements

644 minified modules weigh 189,745 bytes. Compressed one by one they add up to 119,525 bytes with gzip and 99,805 with brotli.
The same minified modules joined into one file compress to 50,361 bytes with gzip (58% smaller) and 41,504 with brotli.
Bundled by esbuild, with imports resolved and minified, the library is 97,292 bytes, or 35,489 with gzip and 31,205 with brotli.
The median module is 157 bytes minified and 143 bytes after gzip. A file that small has little to repeat inside itself, so compression finds almost nothing.
These sums ignore the 644 requests themselves and the time the browser needs to discover imports level by level.
Compile time in V8

We compiled five libraries with V8 (Node 26.4.0 on an Apple M4 Max), once as shipped and once minified with Terser. The test uses new vm.Script on a source that changes on every run, to avoid the compile cache. Each figure is the median of 30 runs, then the median of five such series.

Improvements

lodash: 533 KiB as shipped and 70 KiB minified. Compile time 1.9 ms against 1.2 ms.
moment.js: 179 KiB and 61 KiB. 1.1 ms against 0.8 ms.
jQuery 4.0.0: 250 KiB and 77 KiB. 1.3 ms against 1.2 ms.
d3: 573 KiB and 272 KiB. 5.7 ms against 4.9 ms.
react-dom, development build: 1,155 KiB and 378 KiB. 6.0 ms against 4.8 ms.
This is the top-level compile only, on a fast machine. Phones are slower, and we did not test one. The V8 team wrote in 2019 that on most connections V8 parses faster than the file downloads, so transfer size stays the larger lever.

What to keep and what to retire in 2026

Keep doing

These steps pay off on every protocol.

Minify JavaScript, CSS, HTML and JSON before compression, with the JavaScript minifier, the CSS minifier, the HTML minifier and the JSON minifier.
Serve brotli to clients that accept it and gzip to the others, as described in the GZIP and Brotli guide.
Bundle into a few files per route, split vendor code from application code and drop code nobody calls (see tree shaking versus minification).
Put a content hash in every file name and cache with a long max-age and the immutable directive.
Inline the critical CSS and load the rest without blocking rendering, as covered in the critical CSS guide.
Send 103 Early Hints or a preload link for the few files the first render needs.
Retire these HTTP/1.1 habits

Each of them made sense with six connections per host. Some now cost you something.

Spreading assets over several hostnames (domain sharding).
Relying on server push.
Drawing a sprite sheet only to cut the request count. Requests are cheap now, and one changed icon invalidates the whole sheet.
Concatenating every script of the site into one file.
Inlining files as Base64 data URIs to avoid a request.

Checking what your server really sends

Which protocol served the page

Two quick checks give the answer, and a third explains why a first visit often shows h2.

In Chrome DevTools, open the Network panel, right-click the column header and enable Protocol. The column shows http/1.1, h2 or h3 for each request.
With curl, request the headers of a file and read the first line. HTTP/2 200 means the connection used HTTP/2.
A server announces HTTP/3 with an alt-svc header. The browser then tries HTTP/3 on a later connection, so the first request of a visit often travels over h2.
On 22 October 2026, fastminify.com answered a curl request over HTTP/2 and announced HTTP/3 in its alt-svc header. Our curl build has no HTTP/3 support, so we could only read the announcement.
Whether the response is compressed, and how

The encoding depends on the header the client sends, so test with the header a browser sends.

Request the file once per encoding you care about, then with the full browser header, and read content-encoding each time.
A server can answer gzip when the client offers both gzip and br. Compare what comes back for br alone and for the full header.
Check that vary: Accept-Encoding is present, so a cache does not hand a brotli body to a client that cannot read it.
Compare the bytes on the wire with the decoded size. The code section below has the curl commands.

Five mistakes that survive the move to HTTP/2

Dropping minification because the protocol is modern

Moving to HTTP/2 or HTTP/3 is not a reason to ship readable code. The protocol has no say in what is inside the file.

Removing the minify step from the build after the CDN switched to HTTP/2.
Sending comments, indentation and long names to production because compression will deal with them.
Testing only on a fast office connection, where the extra bytes stay invisible.
Without minification, the gzip size of our 10 JavaScript files doubles, from 566,618 to 1,142,675 bytes.
Keep the minify step, and measure its effect after compression, not before.
Serving hundreds of modules in production

Native modules and HTTP/2 make it possible to serve source files as they are. The V8 team still recommends bundling for production, and unbundled delivery only for small applications of under about 100 modules.

Shipping an unbundled dependency tree to production.
Counting requests in the Network panel and calling it fine because they are multiplexed.
Forgetting that imports are discovered level by level, so a deep tree adds round trips.
In our lodash-es test, compressing module by module produced 119,525 bytes where one file produced 50,361.
Bundle by route or by feature into a handful of files, and add a modulepreload hint for the critical chunks.
Bundling the whole site into one file

The opposite excess is as common. One bundle for every page means each deploy invalidates the whole cache and each page downloads code it never runs.

A single main.js for all routes.
No content hash in file names, so long caching is impossible.
Vendor code mixed with application code, so a one-line fix re-downloads a framework.
The v8.dev module guide notes that unbundled files can load faster on repeat visits, when the cache is warm.
Split vendor code from application code, name files by content hash and serve them with a long max-age and immutable.
Keeping HTTP/1.1 workarounds

Several habits only made sense with six connections per host. Our Base64 guide covers the size cost of data URIs.

Spreading assets over cdn1, cdn2 and cdn3 hostnames. That gives up the single connection multiplexing relies on and adds DNS and handshake work.
Relying on HTTP/2 server push, which Chrome has not supported by default since version 106.
Inlining images as Base64 data URIs, which makes them about 33% larger and impossible to cache apart from the page.
Serve assets from one hostname, use 103 Early Hints or a preload link for critical files and keep images as files.
Expecting HTTP/3 to fix a heavy page

HTTP/3 improves how a connection recovers from packet loss. It does not shrink a page.

Enabling HTTP/3 on the CDN and skipping the performance work.
Expecting a better Largest Contentful Paint when the cause is a 1 MB script.
Checking only that the protocol column says h3.
Treat HTTP/3 as a free improvement for unreliable networks, and keep the byte budget.

Four claims about HTTP/2, HTTP/3 and file size

HTTP/2 makes minification pointless

The claim treats a faster transport as if it shrank the payload.

False. On our 10 JavaScript files, minification removed 50.4% of the gzip bytes and 46.2% of the brotli bytes. HTTP/2 changes none of those bytes.
Gzip and brotli already do the job of minification

Compression and minification overlap, but they work on different things. Compression removes repetition. Minification removes what the program never needed.

Partly true for CSS, not for JavaScript. Gzip alone shrinks our JavaScript to 20% of its size, and minification then halves what is left. On CSS the second step is worth 6.5%.
With HTTP/2 you should never bundle

Multiplexing removed the limit on parallel requests, not the other costs of a file.

Mostly false. The V8 team still recommends bundling for production, and our 644-module test paid 2.4 times more gzip bytes than the same code in one file. Under about 100 modules, unbundled delivery is acceptable.
HTTP/3 will make a slow page fast

HTTP/3 fixes one network problem, packet loss on a shared connection.

It changes neither the byte count nor the compile time. It helps most where packets are lost. Measure on your own pages before expecting a gain.

Minify and measure with FastMinify

Minify, then compare

The JavaScript minifier, CSS minifier, JSON minifier and HTML minifier run in your browser and show the original size and the output size.

1

Paste or drop the file

Paste the source into the input pane or drop a file. For JavaScript, pick the compression level (conservative, normal or aggressive).

2

Read the Compression panel

The panel shows the original size, the output size and the percentage saved. These are uncompressed sizes, so compare them with the raw figures in this article, not with what travels.

3

Check the console option

The JavaScript minifier removes console calls and debugger statements by default (options Remove Console and Remove Debugger). Switch them off if your code relies on them.

Join several files before minifying

The JavaScript, CSS, HTML, XML, SVG and SQL minifier pages have a Concat files button. It joins the files you choose and minifies the result.

1

Open Concat files

Select Concat files on the minifier page, add your files and put them in the order they must run. Drag and drop sets the order.

2

Concatenate and minify

Run Concatenate & Minify. For JavaScript the tool joins files with a semicolon on its own line, so that one file cannot swallow the next, then minifies the whole. It does not resolve imports, so use your bundler for a real dependency graph (see online minifiers versus build tools).

Reproduce the measurements on your own files

Check protocol and encoding with curl

curl -I sends a HEAD request, and a few servers answer it differently from a GET. Replace the URL with one of your own files.

Basic example

# The first line shows HTTP/2 or HTTP/3 curl -sI https://example.com/app.js | head -n 1 # Is HTTP/3 announced? Look for an alt-svc header curl -sI https://example.com/app.js | grep -i alt-svc # Encoding picked by the server: br alone, then the browser header curl -sI -H 'Accept-Encoding: br' https://example.com/app.js | grep -i content-encoding curl -sI -H 'Accept-Encoding: gzip, deflate, br, zstd' https://example.com/app.js | grep -i content-encoding # Bytes on the wire, then decoded bytes curl -s -H 'Accept-Encoding: gzip' -o /dev/null -w '%{size_download}\n' https://example.com/app.js curl -s --compressed https://example.com/app.js | wc -c
Measure raw, gzip and brotli sizes in Node

Save the script as size.mjs. It uses gzip level 9 and brotli quality 11, the settings of the benchmark.

Basic example

import { readFileSync } from 'node:fs' import { gzipSync, brotliCompressSync, constants } from 'node:zlib' const file = process.argv[2] const buf = readFileSync(file) const gzip = gzipSync(buf, { level: 9 }).length const brotli = brotliCompressSync(buf, { params: { [constants.BROTLI_PARAM_QUALITY]: 11, [constants.BROTLI_PARAM_SIZE_HINT]: buf.length, }, }).length console.log(`${file}: raw ${buf.length}, gzip ${gzip}, brotli ${brotli}`)

Usage

node size.mjs dist/app.js node size.mjs dist/app.min.js
Compare many files with one file

This measures compression only. Joining ES modules this way does not produce a working script, so use it on a folder to see the cost of tiny files.

Basic example

import { readdirSync, readFileSync } from 'node:fs' import { join } from 'node:path' import { gzipSync } from 'node:zlib' const dir = process.argv[2] const files = readdirSync(dir) .filter((name) => name.endsWith('.js')) .map((name) => readFileSync(join(dir, name))) const oneByOne = files.reduce((sum, buf) => sum + gzipSync(buf, { level: 9 }).length, 0) const asOneFile = gzipSync(Buffer.concat(files), { level: 9 }).length console.log(files.length, 'files', { oneByOne, asOneFile })
Measure compile time in V8

The script compiles a classic script without running it. new Script does not accept ES modules with import or export.

Basic example

import { readFileSync } from 'node:fs' import { Script } from 'node:vm' const source = readFileSync(process.argv[2], 'utf8') const times = [] for (let i = 0; i < 33; i++) { // a unique trailing comment defeats the compile cache const start = performance.now() new Script(`${source}\n//run${i}`) times.push(performance.now() - start) } times.splice(0, 3) times.sort((a, b) => a - b) console.log(`median ${times[Math.floor(times.length / 2)].toFixed(2)} ms`)

Keep minifying and drop the HTTP/1.1 habits

HTTP/2 and HTTP/3 made small requests cheap. They did not make large files small. Minification still halves the compressed size of unminified JavaScript, trims a few percent from CSS and reduces compile work. What lost its value is the HTTP/1.1 toolkit of sharded hostnames, push and giant bundles. Minify, compress with brotli, bundle sensibly and judge the result by the bytes that travel.

Keep minifying JavaScript, CSS, HTML and JSON before compression.
Serve brotli with a gzip fallback, and test what your server picks for the browser header.
Bundle to a handful of cached files per route, not to one file and not to hundreds.
Retire domain sharding and server push, and use 103 Early Hints or preload for critical files.
Read gains after compression and on the wire, and test on a slow phone before claiming a speed win.
Share this article
Share this article: