Cloudflare has dozens of switches labelled "speed", and most guides tell you to turn all of them on. Some of them help, some do nothing for a modern site, and one of them can break a React app outright. This is the configuration this site actually runs, why each setting is on or off, and a compression benchmark from our own server comparing gzip, Brotli and Zstandard (zstd) - because "which compression should I use" is the question the dashboard never answers with numbers.
The benchmark: identity vs gzip vs Brotli vs zstd
We load-tested our own origin four times, changing only the encoding the client asked for. These are the raw figures, one run per encoding:
| Encoding | Body transferred | Requests / sec | CPU |
|---|---|---|---|
| identity (no compression) | 628.20 MB | 1,935 | 124.5% |
| gzip | 67.25 MB | 1,682 | 143.5% |
| br (Brotli) | 57.79 MB | 2,029 | 171.5% |
| zstd | 65.38 MB | 2,004 | 182.4% |
CPU above 100% means more than one core was busy. Worked through, the table says more than it first appears to:
- Any compression removes about 90% of the bytes. gzip cut the body by 89.3%, zstd by 89.6%, Brotli by 90.8%. The difference between "no compression" and "some compression" dwarfs the difference between the algorithms.
- Brotli was the smallest by a clear margin: 14.1% smaller than gzip and 11.6% smaller than zstd. zstd was only 2.8% smaller than gzip.
- Brotli also served the most requests per second - 2,029, about 5% more than sending the files uncompressed. Sending 628 MB costs more than compressing it to 58 MB, so on this server compression paid for itself in throughput.
- gzip was the slowest, at 1,682 requests per second - 13% below identity. That is a property of this server's gzip setup rather than gzip in general, but it is the one number here that surprised us.
- Per request, zstd cost the most CPU. Dividing CPU by throughput gives roughly 64 units per 1,000 requests for identity, 85 for gzip, 84.5 for Brotli and 91 for zstd. Brotli did more work than gzip in total only because it handled more requests.
The usual caveat applies: this is one run per encoding, on one server, for this site's responses (HTML, JSON and JavaScript, which compress well). Run it on your own stack before quoting it as a law. Still, the result matches what the algorithms are designed for. zstd's strength is very fast compression and decompression, which matters most for data compressed on the fly at high volume. Brotli's strength is ratio, and for web pages that are compressed once and cached, ratio is what reaches the visitor.
What this means behind Cloudflare
With Cloudflare in front, there are two separate hops: your origin to Cloudflare, and Cloudflare to the visitor. The visitor only ever sees the second one. Cloudflare decompresses what your origin sends and re-compresses it for each browser in whatever that browser supports, so the setting that decides what visitors download lives in Cloudflare, not on your server.
That setting is a Compression Rule (Rules → Compression Rules). We run one rule, "Prefer Brotli", for all requests, with the order Brotli → Zstandard → Gzip. You can check yours with curl:
curl -s -o /dev/null -D - -H "Accept-Encoding: br, zstd, gzip" https://example.com/ | grep -i content-encoding
# content-encoding: br
Ask for only zstd and you get zstd; ask for only gzip and you get gzip. Browsers that support Brotli - every current one - get the smallest file. zstd stays in the list as a fallback rather than first choice: per the table above it is bigger than Brotli for this kind of content and is supported by Chrome and Firefox but not by every browser yet.
On the origin side, compression still matters: it is the bytes between your host and Cloudflare's edge, and on a cache miss the visitor waits for that hop. Enabling Brotli or gzip on IIS, nginx or your Node server is worth doing even behind a CDN.
Cache the HTML, not just the assets
By default Cloudflare caches images, CSS and JavaScript but not HTML. Every page view still goes to your origin, which is why "I added Cloudflare and my TTFB didn't change" is such a common complaint. On shared hosting that is the slowest part of the whole page.
A Cache Rule fixes it. Ours: hostname equals the site, excluding /admin and /api, "Eligible for cache", and the edge TTL taken from the origin's Cache-Control header. The origin then decides what can be cached and for how long - our pages send s-maxage=300 - and anything personalised sends private, no-store and passes straight through. Search and listing pages show cf-cache-status: BYPASS by design.
The catch: purge the cache on every deploy. Cached HTML points at the previous build's JavaScript and CSS files. Next.js, Vite and most modern tools give those files hashed names that change on each build, so after a deploy the old HTML references files that no longer exist and pages load unstyled or throw a ChunkLoadError. We hit exactly that and wrote it up in ChunkLoadError after deploy: the CDN was serving cached HTML. If pages stop updating instead, see why a Next.js page is not updating.
Static assets with hashed names are the opposite case: they can be cached for a year. Check that your build sends Cache-Control: public, max-age=31536000, immutable for them. Ours does, and the edge shows them as HIT.
Settings we turned on
- HTTP/3 (with QUIC) - Network tab. Faster connection setup, and better on lossy mobile connections. You can confirm it with the
alt-svc: h3=":443"response header. - Speed Brain - prefetches pages a visitor is likely to open next using the browser's Speculation Rules. It shows up as a
Speculation-Rulesheader. It only prefetches and is cheap to try. - Always Use HTTPS and an HSTS header - not speed settings as such, but every http-to-https redirect is a wasted round trip.
- The Cache Rule and Compression Rule above. Between them they did more for real-visitor speed than everything else on this list.
Settings we left off, and why
- Rocket Loader. It rewrites every script tag so Cloudflare can load them later. On a React or Next.js site that defers the framework's own hydration code and can break interactivity or analytics in ways that are hard to trace. If your framework already controls script loading, leave Rocket Loader off. If you run WordPress with a pile of plugin scripts, test it on a staging copy first.
- Early Hints (103). Harmless, but it can only send hints your origin provides as
Link: rel=preloadheaders. Ours sends none, so it does nothing here. Check your own response headers before counting on it. - Polish (image optimisation). A paid feature, and unnecessary if your framework already resizes images and serves AVIF/WebP - Next.js's image optimiser does, so we would be paying to do it twice.
- Auto Minify no longer exists - Cloudflare removed it in 2024. Minify in your build step.
Settings that quietly add JavaScript
Some Cloudflare features inject their own scripts into your HTML, and they then show up in PageSpeed Insights as third-party code you never added:
- Email Address Obfuscation (Scrape Shield) adds
email-decode.min.jsto every page that contains an email address. When we checked our own HTML it was still on, and it is on our list to switch off - the contact form is the way to reach us anyway. - Web Analytics with automatic setup injects a beacon script. If you already use Google Analytics, you are measuring the same visits twice.
- Bot protection can add a small challenge-platform script. That one is usually worth keeping.
View source on your own page and search for /cdn-cgi/ to see what Cloudflare is adding.
E-commerce: what you must never cache
A shop changes the rules. The product and category pages are exactly the pages that benefit from HTML caching, but the cart, checkout and account pages must never come from a shared cache - one shopper would see another's basket. If you cache HTML on a store:
- Exclude
/cart,/checkout,/my-account(or your platform's equivalents) and any API paths in the Cache Rule itself, not only in the origin headers. - Bypass the cache for visitors with a session or basket cookie - WooCommerce, for example, sets
woocommerce_items_in_cart- where your Cloudflare plan allows cookie conditions in the rule. - Keep the TTL short on product pages, and purge on price or stock changes, so the cached page never shows a price you no longer honour.
- Do not let Rocket Loader near the checkout. Payment provider scripts are the last thing to defer.
The rest of e-commerce speed - images, third-party widgets and checkout scripts - is covered in our follow-up, how we fixed unused JavaScript and LCP render delay on this site.
Check the result
After changing any of this, measure from outside. Our free website speed test times the request three times and reports the median TTFB, the compression and the HTTP version, and can test every page in your sitemap rather than only the home page. For Core Web Vitals, see the Core Web Vitals guide. A slow TTFB on a cached page usually means the Cache Rule is not matching; a slow TTFB on a BYPASS page is your origin, and no CDN setting will fix that.



Join the conversation
No comments yet — be the first to share what you think.