Small JSON responses (~24KB uncompressed, ~1KB after proxy zstd compression) from our SvelteKit app intermittently take 10-12 seconds to reach the browser, despite the app completing the request in under 11ms. The delay is entirely in the Content Download phase. TTFB is normal. Direct curl to localhost confirms the app is fast. Reproducible on every new browser session.
This is not the “upstream header wait” issue reported in other threads: TTFB is fine. The proxy receives the complete response promptly but holds the body for ~11 seconds before delivering it.
Setup
-
Single machine, Frankfurt region (fra)
-
SvelteKit with adapter-node on port 3000
-
Node.js 22,
KEEP_ALIVE_TIMEOUT=65,HEADERS_TIMEOUT=70 -
swap_size_mb = 512,auto_stop_machines = "off" -
Persistent volume mounted at
/data -
Litestream running for SQLite replication
Reproduction
-
Open a new incognito window
-
Navigate to the app (initial page load is fast)
-
Click any internal link (client-side navigation triggers
__data.jsonfetch) -
The
__data.jsonfetch takes 10-12 seconds -
Subsequent navigations are fast
-
Repeatable on every new session
When testing with 4 separate incognito windows simultaneously, all 4 navigations unblock at the exact same instant, regardless of when each was initiated. This suggests a single blocking event in the proxy layer, not per-connection latency.
Evidence
1. Direct localhost test (via fly ssh console):
fly ssh console -a XXXXXXX -C "curl -s -o /dev/null -w \
'TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\nSize: %{size_download} bytes\n' \
'http://localhost:3000/book/__data.json?x-sveltekit-invalidated=01'"
TTFB: 0.010920s
Total: 0.011045s
Size: 24484 bytes
The app responds in 11ms with a 24KB response body when bypassing the proxy. Consistently 8-11ms across repeated tests.
2. Same request through the Fly.io proxy (Chrome DevTools → Timing):
| Phase | Duration |
|---|---|
| Queueing | 1.35ms |
| Stalled | 0.60ms |
| Request sent | 0.17ms |
| Waiting for server response (TTFB) | 154ms |
| Content Download | 10.94s |
| Total | 11.09s |
3. Server-Timing header confirms fast server-side processing:
server-timing: total;dur=7.9, hooks;dur=0.1, resolve;dur=7.7
The app processed the entire request in 7.9ms. The proxy received the complete response almost immediately (TTFB = 154ms including round-trip), then took 10.94 seconds to deliver the ~1KB compressed body to the browser.
4. Response headers from the slow request:
cache-control: private, no-store
content-encoding: zstd
content-type: application/json
date: Thu, 09 Apr 2026 22:51:08 GMT
fly-request-id: 01KNT7BCQZ192R58K9NQECAFD8-fra
server: Fly/9f41eaedb (2026-04-09)
server-timing: total;dur=7.9, hooks;dur=0.1, resolve;dur=7.7
via: 2 fly.io
Note: content-encoding: zstd is applied by the proxy — the app does not set this header. The proxy compresses the 24KB response to ~1KB, then takes 11 seconds to deliver that 1KB.
5. Server-side logs confirm no blocking:
We added a 500ms-interval event loop lag detector — it never fires during the slow requests. We also log every request with timing. All complete in 2-55ms. During the delay, there are 8-12 second gaps where no requests arrive at the app, despite the user actively clicking. After each gap, a burst of requests appears simultaneously.
What we ruled out on the application side:
| Potential cause | Status | Evidence |
|---|---|---|
| Event loop blocking | Ruled out | 500ms lag detector never fires |
| SQLite WAL checkpoint | Ruled out | WAL stays at 1-32 pages |
| Memory pressure | Ruled out | RSS 130-270MB, heap 28-39MB, stable |
| Keep-alive timeout race | Ruled out | Set to 65s/70s, issue persists |
| Cron jobs competing for proxy | Ruled out | Moved to localhost, issue persists |
| Slow server processing | Ruled out | Every request completes in 2-55ms |
Observations pointing to proxy compression/buffering:
-
The proxy compresses 24KB → ~1KB with zstd, then takes 11 seconds to flush that 1KB to the browser
-
All 4 concurrent test windows unblock at the same instant — a single event releases all pending responses
-
The delay is specifically in Content Download (body delivery), not TTFB (header delivery) — the proxy forwards headers promptly but holds the body
-
fly proxy 13000:3000connects via TCP but immediately resets on HTTP request — could not use this to bypass the edge proxy for comparison
Questions
-
The proxy receives the complete response in ~154ms but takes 10.94s to deliver the 1KB compressed body. What is happening during those 11 seconds?
-
Could the zstd compression pipeline have a flushing or flow control issue? Is there a way to disable proxy-level compression to test?
-
The “all 4 windows unblock simultaneously” pattern suggests a single periodic event in the proxy — could this be connection pooling, HTTP/2 flow control, or response buffering?
-
Can you check the proxy logs for
fly-request-id: 01KNT7BCQZ192R58K9NQECAFD8-fra? -
fly proxy 13000:3000connects via TCP but immediately resets — is this expected, or does it indicate a networking issue on the machine?
Disclosure:
Above issue was rewritten for clarity from my debugging notes by AI.