Files
galaxy-game/tools/dev-deploy/Caddyfile.dev
T
Ilia Denisov b85a9e1b9b fix(dev-deploy): explicit Cache-Control on the UI surface
Caddy's `file_server` did not set Cache-Control on the SvelteKit
build, so browsers fell back to heuristic caching keyed off
Last-Modified. On the long-lived dev environment the heuristic
window leaves the previous deploy's `index.html` cached for
minutes-to-hours, and Safari combined that with stale conditional
requests into a visible multi-second freeze on every reload (the
reproduction was "private window reloads instantly, normal window
hangs; clearing Safari caches restores normal speed"). Push
delivery itself works — heartbeat keeps the SubscribeEvents stream
alive — but the bundle path stalls behind the browser revalidating
a chain of stale chunks.

Mirror the standard SvelteKit cache split inside both Caddyfiles:

- `_app/immutable/*` — hash-named JS/CSS chunks Vite emits with
  content-addressed file names — `Cache-Control:
  public, max-age=31536000, immutable`. Safe to cache forever
  because the name changes whenever the content does, so the next
  deploy serves new files under new URLs.
- Everything else (`index.html` fallback via `try_files`,
  `env.js`, `version.json`, `core.wasm`, `wasm_exec.js`,
  `favicon.svg`) — `Cache-Control: no-cache, must-revalidate`.
  The browser still uses the cached body when the ETag matches,
  but it always asks first; a fresh deploy reaches the user on
  the next reload without a manual cache clear.

Smoke-tested locally: a docker-run Caddy with this config returns
the immutable header only for `/_app/immutable/*` and the
no-cache header for `/index.html`, `/env.js`, and the SPA-fallback
path `/some/route`. The Caddyfile passes `caddy validate` in
both `Caddyfile.dev` and `Caddyfile.prod`; the pre-existing
formatting warning on line 7 is untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:11:09 +02:00

52 lines
1.9 KiB
Caddyfile

# Application-routing Caddy for the long-lived dev environment.
# Listens only on the `edge` Docker network; TLS termination and the
# real `:80`/`:443` listeners belong to the host Caddy in front of us.
#
# `/srv/galaxy-ui` is mounted from the `galaxy-dev-ui-dist` named volume,
# refreshed on every dev-deploy run.
{
auto_https off
}
:80 {
@frontend host www.galaxy.lan
handle @frontend {
root * /srv/galaxy-ui
# SvelteKit emits hash-named JS/CSS chunks under
# `_app/immutable/`; the file name changes whenever the
# content changes, so the browser can cache them forever.
# Without an explicit Cache-Control, Caddy falls back to
# heuristic caching that revalidates on every reload —
# measurably slow on Safari + the long-lived dev stack
# when the cache is warm. Everything else (index.html
# fallback, env.js, version.json, core.wasm,
# wasm_exec.js, favicon.svg) must revalidate so a fresh
# deploy lands without the user having to clear the
# cache by hand.
@immutable path /_app/immutable/*
header @immutable Cache-Control "public, max-age=31536000, immutable"
@dynamic not path /_app/immutable/*
header @dynamic Cache-Control "no-cache, must-revalidate"
try_files {path} /index.html
file_server
encode zstd gzip
}
@api host api.galaxy.lan
handle @api {
# Connect-Web (authenticated) lives on a separate listener
# (`GATEWAY_AUTHENTICATED_GRPC_ADDR=:9090`). Anything else —
# public auth, healthz — is the public REST listener on
# `:8080`. The split mirrors the Vite dev-server proxy in
# `ui/frontend/vite.config.ts`.
@connect path /galaxy.gateway.v1.EdgeGateway/*
handle @connect {
reverse_proxy galaxy-api:9090
}
reverse_proxy galaxy-api:8080
}
}