Follow-on to #287, which fixed the nginx example. This makes the default right
regardless of what sits in front.
The bundle went out uncompressed unless a proxy compressed it. Measured against
the built image, no proxy involved:
Wire
Decoded
before
932,696 B
932,696 B
after
311,204 B
932,696 B
The main script alone goes 662 KB -> 209 KB.
What is deliberately not compressed
The proxy routes — /api/blob, /api/image, /api/ics, /api/upload.
They forward upstream bytes under a content-length copied from Stalwart, and #76 was a silent download truncation caused by exactly that header disagreeing
with its body. Re-encoding them would be safe in principle (the length is
dropped, the response goes out chunked) but they carry attachments and images
that are already compressed — nothing to win, and a scar to respect.
/api/events — Hono already skips text/event-stream by content type. It
is in the list anyway so that changing the push route's content type cannot
quietly start buffering the stream.
/api/health — for the opposite reason: at 47 bytes, gzip made it 73.
Hono's size threshold only applies when a response carries a content-length,
and c.json() does not set one. Every other JSON route stays compressed — a
JMAP response has just as unknown a length and can run to hundreds of KB.
Verified against the built image
assets return Content-Encoding: gzip with Vary: Accept-Encoding
/api/events returns text/event-stream, no Content-Encoding, and
delivered a real StateChange event while messages were being written to the
account
/api/jmap responses compress
health is 47 bytes with or without Accept-Encoding
a gzipped shell decodes byte-for-byte to the uncompressed one
Six tests added. A proxy that already compresses still wins: Hono declines
anything carrying Content-Encoding or Transfer-Encoding, so there is no
double-encoding.
No user-visible strings, so no work in any of the nine catalogues.
Follow-on to #287, which fixed the nginx example. This makes the default right
regardless of what sits in front.
The bundle went out uncompressed unless a proxy compressed it. Measured against
the built image, no proxy involved:
| | Wire | Decoded |
| --- | --- | --- |
| before | 932,696 B | 932,696 B |
| after | **311,204 B** | 932,696 B |
The main script alone goes 662 KB -> 209 KB.
### What is deliberately *not* compressed
**The proxy routes** — `/api/blob`, `/api/image`, `/api/ics`, `/api/upload`.
They forward upstream bytes under a content-length copied from Stalwart, and
#76 was a silent download truncation caused by exactly that header disagreeing
with its body. Re-encoding them would be safe in principle (the length is
dropped, the response goes out chunked) but they carry attachments and images
that are already compressed — nothing to win, and a scar to respect.
**`/api/events`** — Hono already skips `text/event-stream` by content type. It
is in the list anyway so that changing the push route's content type cannot
quietly start buffering the stream.
**`/api/health`** — for the opposite reason: at 47 bytes, gzip made it 73.
Hono's size threshold only applies when a response carries a content-length,
and `c.json()` does not set one. Every other JSON route stays compressed — a
JMAP response has just as unknown a length and can run to hundreds of KB.
### Verified against the built image
- assets return `Content-Encoding: gzip` with `Vary: Accept-Encoding`
- `/api/events` returns `text/event-stream`, no `Content-Encoding`, and
delivered a real `StateChange` event while messages were being written to the
account
- `/api/jmap` responses compress
- health is 47 bytes with or without `Accept-Encoding`
- a gzipped shell decodes byte-for-byte to the uncompressed one
Six tests added. A proxy that already compresses still wins: Hono declines
anything carrying `Content-Encoding` or `Transfer-Encoding`, so there is no
double-encoding.
No user-visible strings, so no work in any of the nine catalogues.
**Merged** 2026-09-05 as coffey-labs/ihasmail@5356e603fe3a
<sub>Rebuilt from: GH Archive, git history, session transcript.</sub>
Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.
Verified the two layers together. With this branch's app-side compression running behind the nginx config from #287, 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, so there is no double-encoding and neither layer has to be removed for the other to be correct.
The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.
148 tests pass.
Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.
**Verified the two layers together.** With this branch's app-side compression running behind the nginx config from #287, 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, so there is no double-encoding and neither layer has to be removed for the other to be correct.
The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.
148 tests pass.
Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.
Verified the two layers together. With this branch's app-side compression running behind the nginx config from #287, 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, so there is no double-encoding and neither layer has to be removed for the other to be correct.
The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.
148 tests pass.
Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.
**Verified the two layers together.** With this branch's app-side compression running behind the nginx config from #287, 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, so there is no double-encoding and neither layer has to be removed for the other to be correct.
The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.
148 tests pass.
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.
Follow-on to #287, which fixed the nginx example. This makes the default right
regardless of what sits in front.
The bundle went out uncompressed unless a proxy compressed it. Measured against
the built image, no proxy involved:
The main script alone goes 662 KB -> 209 KB.
What is deliberately not compressed
The proxy routes —
/api/blob,/api/image,/api/ics,/api/upload.They forward upstream bytes under a content-length copied from Stalwart, and
#76 was a silent download truncation caused by exactly that header disagreeing
with its body. Re-encoding them would be safe in principle (the length is
dropped, the response goes out chunked) but they carry attachments and images
that are already compressed — nothing to win, and a scar to respect.
/api/events— Hono already skipstext/event-streamby content type. Itis in the list anyway so that changing the push route's content type cannot
quietly start buffering the stream.
/api/health— for the opposite reason: at 47 bytes, gzip made it 73.Hono's size threshold only applies when a response carries a content-length,
and
c.json()does not set one. Every other JSON route stays compressed — aJMAP response has just as unknown a length and can run to hundreds of KB.
Verified against the built image
Content-Encoding: gzipwithVary: Accept-Encoding/api/eventsreturnstext/event-stream, noContent-Encoding, anddelivered a real
StateChangeevent while messages were being written to theaccount
/api/jmapresponses compressAccept-EncodingSix tests added. A proxy that already compresses still wins: Hono declines
anything carrying
Content-EncodingorTransfer-Encoding, so there is nodouble-encoding.
No user-visible strings, so no work in any of the nine catalogues.
Merged 2026-09-05 as coffey-labs/ihasmail@5356e603fe
Rebuilt from: GH Archive, git history, session transcript.
Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.
Verified the two layers together. With this branch's app-side compression running behind the nginx config from #287, 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, so there is no double-encoding and neither layer has to be removed for the other to be correct.
The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.
148 tests pass.
Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.
Verified the two layers together. With this branch's app-side compression running behind the nginx config from #287, 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, so there is no double-encoding and neither layer has to be removed for the other to be correct.
The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.
148 tests pass.