Skip to content

Response Compression — Brotli & Gzip in .NET

A typical JSON API response of 50 KB becomes 5–8 KB after Brotli compression. For mobile clients on metered connections, or high-throughput APIs serving thousands of requests per second, that difference directly translates into lower latency, reduced bandwidth costs, and a better user experience.

ASP.NET Core ships a response compression middleware, but enabling it safely requires several decisions: which providers, which MIME types, what about HTTPS and the BREACH attack, should SSE streams be compressed? Getting any of these wrong can break real-time features or introduce a security vulnerability.

Granit.Http.ResponseCompression makes those decisions for you — one line of code, safe defaults, nothing to forget.

  • Brotli first, gzip fallback — modern clients get the best ratio (15–25 % smaller than gzip alone), older clients still get compression
  • HTTPS compression enabled safely — BREACH is mitigated by Granit’s antiforgery tokens, CORS enforcement, and SameSite cookies. For Bearer-token APIs the attack is not even applicable (no secret in the response body)
  • SSE never compressedtext/event-stream is hardcoded as excluded so Server-Sent Events stream in real time without buffering surprises
  • Sensible MIME list — JSON, HTML, CSS, JS, XML, WASM, and SVG out of the box
  • Fast by defaultCompressionLevel.Fastest balances CPU cost and ratio for API workloads; override per provider if you need maximum compression
[DependsOn(typeof(GranitHttpResponseCompressionModule))]
public class AppModule : GranitModule { }

In Program.cs, add the middleware before anything that produces a response body — otherwise those responses won’t be compressed:

app.UseGranitResponseCompression(); // first
app.UseOutputCache();
app.UseAuthorization();
app.MapControllers();

That’s it. Zero configuration needed for the common case.

Override in appsettings.json only when you have a specific reason:

{
"ResponseCompression": {
"EnableForHttps": true,
"EnableBrotli": true,
"EnableGzip": true,
"BrotliLevel": "Fastest",
"GzipLevel": "Fastest"
}
}
PropertyDefaultWhen to change
EnableForHttpstrueSet to false only if you sit behind a reverse proxy that already compresses (Cloudflare, Nginx) and want to avoid double-compression
EnableBrotlitrueDisable if all clients are internal tools that don’t send Accept-Encoding: br
EnableGziptrueRarely — gzip is the universal fallback
BrotliLevelFastestOptimal or SmallestSize for batch endpoints where latency matters less than bandwidth
GzipLevelFastestSame reasoning as Brotli

Compressing responses over HTTPS can theoretically enable the BREACH attack — an attacker who can inject content into the same response as a secret (like a CSRF token) could deduce the secret by observing compressed sizes.

In practice, Granit APIs are protected by multiple layers:

  • Antiforgery tokens are randomized per request — the compression oracle cannot converge on a stable value
  • CORS blocks cross-origin requests by default
  • SameSite cookies prevent cross-site request forgery

For REST or GraphQL APIs using Bearer tokens (JWT), BREACH does not apply at all — the token travels in headers, never in the response body.

Server-Sent Events (text/event-stream) are always excluded from compression. This is not configurable — it’s a safety invariant. Compressing an SSE stream would buffer events and break the real-time contract with clients.

WebSocket frames bypass HTTP response compression entirely (the protocol switches after the initial handshake), so no special handling is needed.

CategoryKey typesPackage
ModuleGranitHttpResponseCompressionModule
OptionsGranitResponseCompressionOptionsGranit.Http.ResponseCompression
ExtensionsAddGranitResponseCompression(), UseGranitResponseCompression()Granit.Http.ResponseCompression