The Caddy example has encode zstd gzip. The nginx example had no compression
directive at all, so anyone following it served every asset uncompressed.
Measured against the built app, through nginx in front of the real container:
Wire
Decoded
before
915,199 B
915,199 B
after
322,873 B
915,199 B
2.8x less on first load, and the gap was invisible without inspecting response
headers.
Two things worth a second look in review
text/javascript is listed explicitly. The server sends scripts with that
type, not application/javascript. A conventional gzip_types list compresses
the stylesheet and leaves the 647 KB script uncompressed — which is exactly
what the first attempt at this change did, and it looked like it was working.
text/event-stream is deliberately absent. Compressing or buffering the
push stream would break it; proxy_buffering off is already set below for the
same reason.
Verified
Ran nginx with this config in front of the container:
/assets/*.js returns Content-Encoding: gzip with Vary: Accept-Encoding
/api/events still returns text/event-stream with no Content-Encoding,
and delivered a real StateChange event as plain text while messages were
being delivered to the account
The Caddy example has `encode zstd gzip`. The nginx example had no compression
directive at all, so anyone following it served every asset uncompressed.
Measured against the built app, through nginx in front of the real container:
| | Wire | Decoded |
| --- | --- | --- |
| before | 915,199 B | 915,199 B |
| after | **322,873 B** | 915,199 B |
2.8x less on first load, and the gap was invisible without inspecting response
headers.
### Two things worth a second look in review
**`text/javascript` is listed explicitly.** The server sends scripts with that
type, not `application/javascript`. A conventional gzip_types list compresses
the stylesheet and leaves the 647 KB script uncompressed — which is exactly
what the first attempt at this change did, and it looked like it was working.
**`text/event-stream` is deliberately absent.** Compressing or buffering the
push stream would break it; `proxy_buffering off` is already set below for the
same reason.
### Verified
Ran nginx with this config in front of the container:
- `/assets/*.js` returns `Content-Encoding: gzip` with `Vary: Accept-Encoding`
- `/api/events` still returns `text/event-stream` with no `Content-Encoding`,
and delivered a real `StateChange` event as plain text while messages were
being delivered to the account
- sign-in through the proxy unaffected
Docs only — no application code changes.
**Merged** 2026-09-05 as coffey-labs/ihasmail@fafeee481ed0
<sub>Rebuilt from: GH Archive, git history, session transcript.</sub>
Reworded the comments after verifying how the two layers interact.
With both this config and the app-side compression in #288 active, an asset comes back single-encoded — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing it, so the two are safe together and the ordering of these PRs does not matter.
The comment no longer claims the bundle ships uncompressed, since that stops being true once #288 lands. It now describes what the directives do without asserting which side does the work.
Reworded the comments after verifying how the two layers interact.
With both this config and the app-side compression in #288 active, an asset comes back **single-encoded** — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing it, so the two are safe together and the ordering of these PRs does not matter.
The comment no longer claims the bundle ships uncompressed, since that stops being true once #288 lands. It now describes what the directives do without asserting which side does the work.
Reworded the comments after verifying how the two layers interact.
With both this config and the app-side compression in #288 active, an asset comes back single-encoded — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing it, so the two are safe together and the ordering of these PRs does not matter.
The comment no longer claims the bundle ships uncompressed, since that stops being true once #288 lands. It now describes what the directives do without asserting which side does the work.
Reworded the comments after verifying how the two layers interact.
With both this config and the app-side compression in #288 active, an asset comes back **single-encoded** — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing it, so the two are safe together and the ordering of these PRs does not matter.
The comment no longer claims the bundle ships uncompressed, since that stops being true once #288 lands. It now describes what the directives do without asserting which side does the work.
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The Caddy example has
encode zstd gzip. The nginx example had no compressiondirective at all, so anyone following it served every asset uncompressed.
Measured against the built app, through nginx in front of the real container:
2.8x less on first load, and the gap was invisible without inspecting response
headers.
Two things worth a second look in review
text/javascriptis listed explicitly. The server sends scripts with thattype, not
application/javascript. A conventional gzip_types list compressesthe stylesheet and leaves the 647 KB script uncompressed — which is exactly
what the first attempt at this change did, and it looked like it was working.
text/event-streamis deliberately absent. Compressing or buffering thepush stream would break it;
proxy_buffering offis already set below for thesame reason.
Verified
Ran nginx with this config in front of the container:
/assets/*.jsreturnsContent-Encoding: gzipwithVary: Accept-Encoding/api/eventsstill returnstext/event-streamwith noContent-Encoding,and delivered a real
StateChangeevent as plain text while messages werebeing delivered to the account
Docs only — no application code changes.
Merged 2026-09-05 as coffey-labs/ihasmail@fafeee481e
Rebuilt from: GH Archive, git history, session transcript.
Reworded the comments after verifying how the two layers interact.
With both this config and the app-side compression in #288 active, an asset comes back single-encoded — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing it, so the two are safe together and the ordering of these PRs does not matter.
The comment no longer claims the bundle ships uncompressed, since that stops being true once #288 lands. It now describes what the directives do without asserting which side does the work.
Reworded the comments after verifying how the two layers interact.
With both this config and the app-side compression in #288 active, an asset comes back single-encoded — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing it, so the two are safe together and the ordering of these PRs does not matter.
The comment no longer claims the bundle ships uncompressed, since that stops being true once #288 lands. It now describes what the directives do without asserting which side does the work.