Page speed, redirects, Core Web Vitals
Enable Brotli compression for smaller text payloads
Brotli compresses HTML, CSS, and JS 15-25% smaller than gzip. Enable it on your CDN or origin to cut bytes over the wire and speed up FCP and LCP.
What's happening
Brotli is Google's compression algorithm, designed specifically for text-based web content. At equivalent CPU cost it produces 15-25% smaller output than gzip; at maximum quality (level 11) it can exceed 30% savings on some payloads. Every evergreen browser since 2017 supports it over HTTPS, advertised via Accept-Encoding: br.
When the browser sends Accept-Encoding: gzip, deflate, br and the server responds with Content-Encoding: gzip, you're leaving Brotli savings on the table. DevTools Network panel shows Content-Encoding per response in the Headers tab. Lighthouse's "Enable text compression" audit doesn't differentiate between gzip and Brotli, but the bytes-on-wire data in WebPageTest does.
Most modern CDNs (Cloudflare, Fastly, Vercel, Netlify) enable Brotli automatically. The problem usually crops up on self-hosted nginx without ngx_brotli compiled in, on Apache without mod_brotli, or on edge functions returning Response objects without Brotli compression configured.
Why it matters
Brotli's extra savings (15-25% on top of gzip) directly speed up FCP and LCP for text-heavy sites. A 200KB gzipped JS bundle becomes 160KB Brotli-compressed — that's 200ms saved on 4G mobile per asset.
Bandwidth cost is the secondary lever. Sites paying for CDN egress save 15-20% on text-asset bills overnight. For a site shipping 5TB/month of text traffic, that's a real number.
Common causes
- Self-hosted nginx without ngx_brotli compiled or installed.
- Apache without mod_brotli enabled (a2enmod brotli).
- Edge function or serverless route returning Response without compression negotiation.
- Pre-compressed static assets only providing.gz files, not.br.
- Custom reverse proxy stripping br from Accept-Encoding before forwarding.
- CDN configured to allow only gzip in cache layer.
Detect this on your site
Run a quick scan with the Speed Test. The tool surfaces this exact issue with the records and context needed to apply the fix below.
Open Speed TestHow to fix it
- 1
Check Content-Encoding in DevTools
Open DevTools Network panel, click an HTML/CSS/JS asset, look at Response Headers. Content-Encoding: br means Brotli; gzip means you're missing out. Run curl -H 'Accept-Encoding: br' -I https://yoursite.com to confirm from CLI.
- 2
Enable Brotli on Cloudflare or Vercel
Cloudflare: Speed → Optimization → Brotli (on by default). Vercel: Brotli is on by default for static and serverless responses. Netlify: same. If you're on these platforms, you probably already have Brotli.
- 3
Install ngx_brotli on nginx
On Debian/Ubuntu: apt install nginx-extras (which bundles ngx_brotli). On other distros, compile nginx with the module. Add to nginx.conf: brotli on; brotli_comp_level 6; brotli_types text/css application/javascript application/json image/svg+xml.
- 4
Enable mod_brotli on Apache
Run a2enmod brotli, then in your VirtualHost: AddOutputFilterByType BROTLI_COMPRESS text/html text/css application/javascript application/json. Restart Apache. Verify with curl.
- 5
Pre-compress static assets at build
Generate.br files at build time with brotli -k -q 11 file.js. nginx with brotli_static on; serves them when the client sends Accept-Encoding: br. Build-time level 11 squeezes 5-10% more than runtime level 6.
- 6
Configure edge function compression
Cloudflare Workers, Vercel Edge, and Fastly Compute@Edge handle compression negotiation when you return Response with text Content-Type. For binary or custom encodings, manually compress with the CompressionStream API: new CompressionStream('br') wraps a stream.
- 7
Don't compress already-compressed formats
JPEG, PNG, WebP, AVIF, MP4, WOFF2, ZIP — already compressed. Re-compressing wastes CPU and grows bytes. Whitelist only text MIME types in nginx's brotli_types directive.
Example
# /etc/nginx/conf.d/brotli.conf
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
http {
brotli on;
brotli_static on; # serve pre-compressed .br files
brotli_comp_level 6; # 1-11; 6 is a good runtime balance
brotli_min_length 256;
brotli_types
text/plain text/css text/xml
application/javascript application/json application/xml
application/atom+xml application/rss+xml
image/svg+xml font/woff font/woff2;
}
# Build-time pre-compression script:
# find ./public -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) \
# -exec brotli -k -q 11 {} \;nginx with Brotli runtime compression plus build-time level-11 static.
Frequently asked
Runtime: level 4-6 (cheap 95% of the savings). Build-time pre-compression: level 11 (slow but maximum savings paid once at build). Level 11 at runtime adds 100ms+ per response — don't.
Brotli works over HTTP technically but browsers only negotiate it over HTTPS for security reasons. If you're on HTTP you're missing more than Brotli — you're missing HTTP/2 HTTP/3 and basic security.
No. Keep both. Browsers send Accept-Encoding: gzip deflate br and the server picks the best supported. Older clients fall back to gzip; modern ones get Brotli.
Related fixes
Page speed, redirects, Core Web Vitals
Enable Brotli or gzip to compress text assets
Page speed, redirects, Core Web Vitals
Enable HTTP/2 or HTTP/3 to multiplex requests
Page speed, redirects, Core Web Vitals
Use a CDN to cut latency and offload origin traffic
Page speed, redirects, Core Web Vitals
Fix FCP slow: get First Contentful Paint under 1.8s