Compress the bundle in the nginx example #287

Closed
opened 2026-09-06 05:44:35 +00:00 by jcoffey-dev · 2 comments
Owner

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@fafeee481e

Rebuilt from: GH Archive, git history, session transcript.

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>
Author
Owner

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.
Author
Owner

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.