Deployed build had MAP_TILE_URL truncated to 'https://.../tile/{z' —
Leaflet then URL-encoded it to '%7Bz' and fetched garbage tile paths.
Root cause: build-docker.sh used
: "\${MAP_TILE_URL:=https://flights.test.aeroflot.ru/map/api/tile/{z}/{x}/{y}.jpeg}"
and bash parameter expansion terminates the default value at the
FIRST unescaped '}', leaving '{z' and discarding the rest. The env
passed to `pnpm build:standalone` was already truncated, so every
downstream step (base64 encode → HTML inject → client decode) faithfully
carried the broken value through.
Fix by moving the defaults to Dockerfile's ARG lines — ARG defaults
are plain strings, not shell-parsed — and simplify build-docker.sh to
only forward MAP_TILE_URL / API_BASE_URL as --build-arg when the
caller explicitly sets them. Quote the k8s env values for defensive
YAML hygiene as well.
Two gaps blocked http://flights-ui.devwebzavod.ru/ru/flights-map:
1. The inline <script>window.__ENV__=...</script> was written with the
Leaflet tile template ('/map/api/tile/{z}/{x}/{y}.jpeg') embedded
directly. Rspack's html-plugin pre-processes the children string and
ate the '{z}' placeholder, truncating the injected JS literal to
'/map/api/tile/{z' — MAP_TILE_URL on the client ended up broken and
getEnv() fell back to the default.
Escape every '{'/'}' inside the stringified value as '\u007B'/'\u007D'.
JS decodes the Unicode escapes back to '{}' at parse time; the html
plugin's template engine sees no placeholders to eat. Object-literal
braces outside the string stay raw (Unicode escapes aren't valid in
operator positions in JS source).
2. API_BASE_URL was still hard-defaulting to 'http://localhost:8080/api',
so every dictionary fetch on the deployed cluster died with
ERR_CONNECTION_REFUSED. Thread API_BASE_URL through the same
PUBLIC_ENV_KEYS/html.tags path as MAP_TILE_URL, add matching Docker
ARG/ENV, and forward it in deployment/build-docker.sh + k8s manifest.
The devwebzavod default for both is https://flights.test.aeroflot.ru
— where the real Aeroflot ingress terminates /map/api/** and /api/**.
Prod keeps overriding with same-origin URLs.
http://flights-ui.devwebzavod.ru/ru/flights-map was still hitting the
same-origin tile path after adding the k8s env: Modern.js renders the
<Suspense> fallback on the server (i18n isn't preloaded), so the route
component that reads getEnv() never actually runs during SSR. The page
hydrates client-side, where process.env is Rspack's empty stub and
MAP_TILE_URL is never set — getEnv() falls back to the default.
Move the value into window.__ENV__ instead:
- modern.config.ts: inline a <script> into html.tags that sets
window.__ENV__ = { MAP_TILE_URL: <value> } at SSR-server startup.
The snippet is authored once at server boot, so the HTML template
baked into dist/standalone/html/main/index.html always carries the
pod's tile URL.
- src/env/index.ts: merge window.__ENV__ on top of process.env so the
browser prefers the injected value (process.env only has NODE_ENV
after Rspack's polyfill).
- Dockerfile.react: accept MAP_TILE_URL as a build ARG and expose it
as ENV before `pnpm build:standalone`, so Modern.js picks it up when
building the HTML template. k8s env still flows into the Node SSR
process; the build-arg path guarantees correctness even when the
runtime env is stripped.
- deployment/build-docker.sh: forward MAP_TILE_URL through as a
build-arg (default keeps the same-origin path). CI on the
devwebzavod cluster can export MAP_TILE_URL=https://flights.test.aeroflot.ru/map/api/tile/{z}/{x}/{y}.jpeg
before running build-docker.sh and the resulting image will serve
tiles from the upstream the real Aeroflot ingress terminates.
The config/public/ directory (fonts, images, leaflet icons, favicons) is
Modern.js's publicDir convention — copied into dist/standalone/public/ at
build time. Two pre-existing gaps caused this to break on the deployed
SSR image and any fresh sync:
- scripts/sync-to-flights-front.sh did not copy config/ to the target
repo, so the flights-front tree was missing /assets/** entirely.
- Dockerfile.react only copied src/, skipping config/; pnpm
build:standalone ran without a publicDir source.
Result was that every /assets/** URL served the SSR HTML index with
Content-Type: text/html, producing OTS font-parse errors
(sfntVersion 1008821359 == '<!DT') and silently broken images.
Fix mirrors what was applied ad-hoc in Aeroflot.Flights.Front; this makes
future syncs and Docker builds carry the assets automatically.