Add a Caddy reverse proxy behind a new `--profile serve-static` so a
contributor can exercise the production build with the per-locale
routing that the on-prem reverse proxy will use (ADR-0019). Closes
the gap PR #96 surfaced: the locale switcher / route fusion / cookie
plumbing all need a prod-faithful local setup, and `nx serve-static`
falls short (no SPA fallback per locale, no smart `/` redirect).
What lands:
- `infra/local/Caddyfile` — three-block route:
* `GET /` → smart redirect to `/{locale}/` based on
`Accept-Language` (FR fallback, since the APF audience is
francophone). Cookie support waits on the BFF route in
ADR-0019.
* `/fr/*` → serves `dist/.../browser/fr/` with SPA fallback to
`index.html` for deep links.
* `/en/*` → mirror.
* Catch-all → bounces to `/fr/` so a typo can't land on the
Caddy directory-listing footgun (same family as the perf-gate
fix in #92).
- `infra/local/dev.compose.yml` — new `serve-static` service on the
`serve-static` profile, bind-mounting the Caddyfile and the
`dist/apps/portal-shell/browser/` folder (read-only). Default
port 4200, override via `SERVE_STATIC_PORT` in `.env`.
- `infra/local/.env.example` — adds `SERVE_STATIC_PORT=4200`.
- `infra/local/dev.sh` — registers `serve-static` in
`ALL_PROFILES` so `dev.sh down|status|logs` catches the new
container, and `dev.sh up serve-static` works.
- `infra/README.md` — adds the new file row, the workflow in the
"First-time setup" snippet, the cheat-sheet row, and the service
endpoint row with the prerequisite `nx build … -c=production`.
Workflow once merged:
```
pnpm exec nx build portal-shell --configuration=production
./infra/local/dev.sh up serve-static
open http://localhost:4200/ # → /fr/ or /en/ per Accept-Language
```
Verified locally:
- GET / with Accept-Language=fr → 302 /fr/
- GET / with Accept-Language=en → 302 /en/
- GET /unknown → 302 /fr/
- GET /fr/deep/route → 200 (SPA fallback to fr/index.html)
- GET /fr/favicons/favicon.svg → 200 (per-locale asset served)
- /fr/index.html has `lang="fr"` and `<base href="/fr/">`
- /en/index.html has `lang="en"` and `<base href="/en/">`
This is local convenience only — no TLS, no auth, binds to
localhost. The on-prem reverse proxy gets its own ADR (phase 3b).