Response Compression
app.UseResponseCompression();That is the whole setup. Brotli, gzip and deflate are negotiated from Accept-Encoding, and text-ish
content types are compressed.
This is worth more here than in a datacentre. An embedded server is reached over cellular or a tunnel, where the CPU is idle and the bytes are not.
Configuration
Section titled “Configuration”builder.AddResponseCompression(o =>{ o.Level = CompressionLevel.Optimal; o.MinimumBytes = 512; o.MimeTypes.Add("application/vnd.myapp+json");});
app.UseResponseCompression();Or inline, for a server with no container:
app.UseResponseCompression(o => o.Level = CompressionLevel.SmallestSize);| Property | Default | Notes |
|---|---|---|
Providers |
brotli, gzip, deflate | Best first; remove one to stop offering it |
MimeTypes |
text/*, JSON, XML, JS, wasm, SVG, ndjson | Exact, or type/* prefix |
ExcludedMimeTypes |
text/event-stream |
Never compressed |
MinimumBytes |
1024 | Only applies when the length is known up front |
Level |
Fastest |
|
EnableForHttps |
true |
See below |
ShouldCompress |
null |
A predicate that overrides all of the above |
MimeTypes is everything text or text-shaped. Images, video, fonts and archives are already
compressed, and running them through gzip spends CPU to make them very slightly larger. MinimumBytes
exists for the same reason at the other end: below roughly a packet’s worth there is nothing to win.
text/event-stream is excluded because compressing it is legal and usually wrong — the compressor
buffers, and a stream whose whole purpose is immediacy arrives in bursts instead.
Negotiation
Section titled “Negotiation”Accept-Encoding is parsed with q-values. q=0 is a refusal, not a weak preference. Higher q
wins; equal q falls back to the provider’s own priority, which is how gzip, br — no stated
preference — still picks brotli. A bare * accepts anything not otherwise named, so the server’s
first choice applies.
How it works
Section titled “How it works”The decision is made on the first byte written, not when the middleware runs — nothing has set a content type yet at that point.
It works by wrapping the response body control, so every write path funnels through one place: a
handler using Body, BodyWriter, WriteAsync, a result type or the static file middleware is
covered without knowing this exists.
Compressing clears the declared Content-Length, since the compressed size is unknown until the last
block. Ranges, pre-encoded bodies, 204/304, HEAD and already-compressed media types are all passed
through untouched.
Vary: Accept-Encoding is appended from an OnStarting callback either way — appended rather
than set, because a handler’s own Vary is not something to trample, and from a callback because
setting it before the handler runs would let the handler overwrite it.
Ordering
Section titled “Ordering”Put it early. It has to wrap everything that writes a body, including static files:
app.UseCors();app.UseResponseCompression();app.UseStaticFiles("./wwwroot");HTTPS and BREACH
Section titled “HTTPS and BREACH”EnableForHttps is on by default, because a tunnelled server is HTTPS end to end and switching
compression off there would mean never compressing at all.
Precompressed files
Section titled “Precompressed files”For content published with .br/.gz sidecars — a Blazor publish does this — serving those beats
recompressing per request: they were compressed once at maximum effort, and this middleware runs at a
level chosen for speed. Turn on ServePrecompressedFiles in
static files (and Blazor does it for you).
Reading compressed request bodies
Section titled “Reading compressed request bodies”app.UseRequestDecompression();The mirror of the above, and on a device the more valuable direction: uplink is the slow, expensive, battery-hungry half of a mobile connection, and a sync client posting a batch of JSON is exactly the shape that compresses well.
Put it before anything that reads a body — model binding, the multipart reader, a body-logging middleware that would otherwise record compressed bytes.
Brotli, gzip and deflate are read by default. The Content-Encoding and Content-Length headers are
removed once the body is swapped, because both describe bytes that are no longer there.
| Property | Default | Notes |
|---|---|---|
Providers |
br, gzip, deflate | |
MaxDecompressedBytes |
Limits.MaxRequestBodySize |
See below |
RejectUnsupportedEncodings |
true |
Otherwise the body is passed through as sent |
An encoding no provider handles is refused with 415 and an Accept-Encoding header saying what
would have worked. A stack of codings (gzip, br) is refused the same way: it is legal, vanishingly
rare, and half-handling it would be worse than saying so.


