Skip to content
Shiny.NET

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.

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.

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.

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.

Put it early. It has to wrap everything that writes a body, including static files:

app.UseCors();
app.UseResponseCompression();
app.UseStaticFiles("./wwwroot");

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.

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).

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 The request’s limit: Limits.MaxRequestBodySize, or the route’s own limit 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.