HTML Pages Are Served Uncompressed, Wasting Bandwidth on Every Visit
Why is my page's HTML response so much larger than it needs to be? Your server is sending HTML with no content-encoding, usually because the app assumed the reverse proxy compresses responses and the proxy has no encode directive configured, or the opposite happened. Check with curl -sI -H 'Accept-Encoding: gzip, br' against the live URL. Enable gzip or brotli in exactly one layer, proxy or app, and measure the byte delta before and after.
Seen in 11 of 450 scanned projects (2%). Counts projects where curl -sI -H 'Accept-Encoding: gzip, br' against the live HTML URL returned no content-encoding header.
How to tell you have it
- curl -sI -H 'Accept-Encoding: gzip, br' against the live URL shows no content-encoding header
- A large inline HTML shell is resent uncompressed on every single page load
- Static JS and CSS assets download uncompressed even though the HTML they load is fine
- Page weight looks fine locally but is several times larger when checked against the deployed URL
- The reverse proxy config has no gzip or encode directive and the app framework also disabled its own compression
Why it matters
Compression is one of the highest ratio wins available for web performance: gzip typically shrinks HTML and JSON by 60-80%, brotli slightly more. When it's missing, every visitor pays the full byte cost on every request, which matters most on mobile connections and matters doubly for crawlers and AI agents fetching a page repeatedly.
The most common cause is a coordination gap between two layers that each assumed the other handles it. The app disables its own compression middleware because 'the proxy does that,' and the proxy config was copied from a template that never turned gzip on. Neither layer is wrong on its own; the gap between them is the bug.
The opposite mistake, compressing inside an already memory-pressured Node process under load, trades one problem for another: compression is CPU-bound, and doing it in the same process that's serving requests can turn a bandwidth win into a latency loss during traffic spikes. The fix is to pick one layer deliberately, not to enable it everywhere.
Uncompressed static assets compound the problem because they're the largest, most cacheable bytes on the page, fonts, bundled JS, vendor CSS, and they get downloaded on first visit by every new user. A missing encode directive on the static file server silently doubles or triples time-to-interactive for anyone not already cached.
How Heygents detects it
Deep Scan runs curl -sI -H 'Accept-Encoding: gzip, br' against the live URL and checks the response for a content-encoding header, then compares the raw and would-be-compressed byte sizes of curl -s output for the HTML body. It also reads the reverse proxy config file and the app's server entrypoint for compression middleware or an encode directive.
How to fix it
- Confirm the gap with curl Request the live page with an explicit Accept-Encoding header and check whether the response actually comes back compressed. No content-encoding header in the response means nothing in the chain is compressing it.
- Pick exactly one compression layer Decide whether the reverse proxy or the app process will own compression, and disable it in the other. Running compression in both places is harmless but wasteful; running it in neither is the actual bug you're fixing.
- Enable it in the proxy (Caddy example) If the reverse proxy is the chosen layer, add an encode directive. Caddy supports this natively and it applies to every route behind it without touching app code.
- Or enable it in the app (Express example) If the app owns compression instead, add the standard compression middleware ahead of the routes it should apply to, and confirm the proxy config does not also try to compress.
- Measure the byte delta Compare response size before and after the change on the same URL, and confirm the content-encoding header is present in production, not just in local dev.
Confirm the gap with curl
curl -sI -H 'Accept-Encoding: gzip, br' https://example.com/ | grep -i content-encoding
Enable it in the proxy (Caddy example)
example.com {
encode zstd gzip
reverse_proxy localhost:3000
}
Or enable it in the app (Express example)
const compression = require('compression');
app.use(compression());
Measure the byte delta
curl -s -H 'Accept-Encoding: gzip' https://example.com/ -o /tmp/out.gz -w '%{size_download}\n'
curl -s https://example.com/ -o /tmp/out.raw -w '%{size_download}\n'
Find this in your own projects, automatically
Heygents runs a read-only Deep Scan across every project you own, finds issues like this one, and hands you a ready-to-run fix an AI agent can execute and verify. A solo developer gets the audit, the backlog and the fix loop in one place.
Open Heygents →Frequently asked questions
Should I compress in Node or in the reverse proxy?
The reverse proxy is usually the better choice because it's written in a compiled language, is not competing with your application's event loop, and applies uniformly to every route without a middleware ordering bug taking it out. Reach for app-level compression only if you don't control the proxy layer.
Does brotli replace gzip or sit alongside it?
Alongside it. Brotli compresses slightly better but isn't supported by every client, so the server should offer both and let content negotiation via the Accept-Encoding header pick the best one each client supports. Caddy and nginx with the brotli module both handle this automatically.
Will compression break caching or ETags?
No, but make sure your Vary header includes Accept-Encoding if you're caching compressed and uncompressed variants separately. Without it, a shared cache can serve a gzip response to a client that didn't ask for one, or vice versa. Note that some servers weaken a strong ETag to a weak one when they compress, so compare ETag values before and after enabling compression if conditional requests matter to you.
Why is the HTML fine but my bundled JS still huge over the wire?
Static file serving is often configured separately from dynamic route compression. Check the static asset middleware or CDN config specifically; it's common to fix compression for HTML responses and forget the static file handler serving the JS and CSS bundles has its own separate configuration.