Skip to content

feat: let the client be built for a sub-path - #17

Merged
RohanReddy44 merged 3 commits into
mainfrom
feat/base-path-buildtime
Sep 23, 2026
Merged

RohanReddy44 merged 3 commits into
mainfrom
feat/base-path-buildtime

Conversation

@RohanReddy44

Copy link
Copy Markdown
Collaborator

Replaces #16, which did the same thing at container start. The path is known at build time, so this is the smaller mechanism and the conventional one.

The client assumed it owned the origin root. Behind a shared domain at /experience, assets, fonts, config.json and the API were all absolute from / and every one of them 404'd — and the config failure was silent, because an unreachable config.json falls back to the bundled defaults.

How it works

docker build --build-arg VITE_BASE_PATH=/experience/ -t experience-ui .
location /experience/ {
    proxy_pass http://experience-ui:8080/;   # trailing slash strips the prefix
}
location = /experience {
    return 301 /experience/;                 # otherwise a bare /experience 404s
}

Default is / and nothing changes. In CI it comes from a VITE_BASE_PATH repository variable.

The trade: the path is baked into the bundle, so an image built for a sub-path serves only from that path. A deployment needing two paths builds two images. That is the cost of the simpler mechanism, and it is fine while the path is known when the image is built.

What Vite does not handle

It rewrites index.html, imported assets and CSS url(). Three things were invisible to it:

  • The API base and config.json URL — now import.meta.env.BASE_URL-prefixed
  • Six icon paths in the header and recording controls — string literals pointing at public/, so only those images broke while everything else worked
  • Brand asset paths from config.json — these are data, not code. A CDN URL is left alone; a path is resolved against the base

The one that looked like a different bug

The router matched against the origin root. Under a prefix the page loaded, the config resolved, React mounted — and then every route 404'd. It now takes basepath from import.meta.env.BASE_URL. Worth knowing because "the assets load" is not evidence that it works.

Verified

A second nginx container as the edge proxy, both ways:

built for /experience/ default build at /
page, /chat route 200, renders 200, renders
config.json 200 200
brand/logo.png 200 200
assets/settings.svg 200 200
fonts/poppins-400.ttf 200 —

Screenshotted at the sub-path, since status codes would not have caught either of the two real bugs.

Operationally

nginx resolves a proxy_pass hostname once at config load. Restart the app container and the proxy keeps the old address — I hit a 502 from exactly this while testing, and it looks like the app is down when it is not. Reload the edge after redeploying.

🤖 Generated with Claude Code

RohanReddy44 and others added 3 commits September 23, 2026 18:30
It assumed it owned the origin root. Behind a shared domain at
/experience, assets, fonts, config.json and the API were all absolute
from /, so every one of them 404d — and the config failure was silent,
because an unreachable config.json falls back to the bundled defaults.

VITE_BASE_PATH is a Docker build arg, baked into the bundle. Default "/"
is the origin root and nothing changes. An image built with a sub-path
serves only from that path, so a deployment needing two paths builds two
images; the path is known at build time, so that is an acceptable trade
for the smaller mechanism.

Vite rewrites what it can see — index.html, imported assets, CSS urls.
Three things it cannot see needed the prefix by hand:

- the API base and config.json URL
- six icon paths in the header and recording controls, which are string
  literals pointing at public/
- the brand asset paths from config.json, which are data. A CDN URL is
  left alone; a path is resolved against the base

The router needed it too. Without a basepath it matches against the
origin root, so under a prefix the page loads, the config resolves and
React mounts — and then every route 404s, which looks like a different
bug entirely.

Verified with a second nginx container as the edge proxy: built for
/experience/ and served through it, and the default build served at the
root, checking the page, the /chat route, config.json, the logo, the
icons and the fonts in both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers the build arg, the two nginx blocks it needs, and the constraint
that an image built for a sub-path serves only from that path.

Also notes the trap that cost time while testing: nginx resolves a
proxy_pass hostname once at config load, so restarting the app container
gives a 502 that looks like the app being down until the proxy is
reloaded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit resolved brand asset paths through a helper that
stripped leading slashes and skipped anything that looked like a URL. The
browser already does this: a relative URL resolves against the page, an
absolute one is left alone. The helper was guarding against nothing, and
I had it duplicated in two files.

Config values are now read straight through. The contract is stated in
the README instead: a file under public/ is referenced without a leading
slash, and anything else is a full URL.

import.meta.env.BASE_URL stays where the path is code rather than config
— the API base, the config.json URL, the router basepath and the six
public/ icon paths. That is Vite's own idiom for exactly this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@RohanReddy44
RohanReddy44 merged commit dd3cc81 into main Sep 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant