[BUG] Newly-scaffolded projects ship with VITE_BASE='/' — assets 404 behind Apache prefix #6

Closed
opened 2026-04-25 21:07:20 +02:00 by uwe.admin · 3 comments
Owner

Symptom

A project provisioned via new-project.sh and deployed behind the standard Apache vhost (path-based routing at /projects/<name>/) serves a blank page on every SPA route. The HTML loads fine:

<script type="module" src="/assets/index-Sp9STMO4.js"></script>

but the asset URL is root-relative. The browser fetches https://uwe-schuster.info/assets/index-Sp9STMO4.js (404), not https://uwe-schuster.info/projects/palibu/assets/index-Sp9STMO4.js (where it actually lives).

Reproduced live on palibu after the recent recreation: HTML at /projects/palibu/set-password?token=… is HTTP 200, but the page is blank because the bundled JS never loads.

Cause

Vite needs base: '/projects/<name>/' at build time so the emitted <script src> includes the prefix. The Apache vhost template already documents this (apache/projects-*.conf notes VITE_BASE is the matching knob), but new-project.sh / the build step is not wiring VITE_BASE through to the actual vite build invocation.

A "Scaffold: pin VITE_BASE for Apache proxy prefix" commit landed in palibu (commit 74d2b79) but evidently did not propagate to the scaffold itself or to the build pipeline that produced the currently-deployed bundle.

Acceptance

  • A project provisioned by new-project.sh foo and built via the standard CMake target produces HTML referencing /projects/foo/assets/… (not /assets/…).
  • The deployed app loads the SPA on every public route (/, /set-password, /guest, /admin).
  • Existing local-dev workflow (no Apache, no prefix) keeps working — VITE_BASE defaults to / when unset.

Follows the oatpp-authkit fix that resolved the prior /set-password?token=… 401 (oatpp-authkit 46971ac, tagged v0.3.3). With auth fixed, this VITE_BASE bug is now the next thing blocking the password-reset flow on a freshly-provisioned project.

See also the companion issue for an end-to-end test that would have caught this before deploy.

## Symptom A project provisioned via `new-project.sh` and deployed behind the standard Apache vhost (path-based routing at `/projects/<name>/`) serves a blank page on every SPA route. The HTML loads fine: ```html <script type="module" src="/assets/index-Sp9STMO4.js"></script> ``` but the asset URL is root-relative. The browser fetches `https://uwe-schuster.info/assets/index-Sp9STMO4.js` (404), not `https://uwe-schuster.info/projects/palibu/assets/index-Sp9STMO4.js` (where it actually lives). Reproduced live on palibu after the recent recreation: HTML at `/projects/palibu/set-password?token=…` is HTTP 200, but the page is blank because the bundled JS never loads. ## Cause Vite needs `base: '/projects/<name>/'` at build time so the emitted `<script src>` includes the prefix. The Apache vhost template already documents this (`apache/projects-*.conf` notes `VITE_BASE` is the matching knob), but `new-project.sh` / the build step is not wiring `VITE_BASE` through to the actual `vite build` invocation. A "Scaffold: pin VITE_BASE for Apache proxy prefix" commit landed in palibu (commit `74d2b79`) but evidently did not propagate to the scaffold itself or to the build pipeline that produced the currently-deployed bundle. ## Acceptance - A project provisioned by `new-project.sh foo` and built via the standard CMake target produces HTML referencing `/projects/foo/assets/…` (not `/assets/…`). - The deployed app loads the SPA on every public route (`/`, `/set-password`, `/guest`, `/admin`). - Existing local-dev workflow (no Apache, no prefix) keeps working — `VITE_BASE` defaults to `/` when unset. ## Related Follows the oatpp-authkit fix that resolved the prior `/set-password?token=…` 401 (oatpp-authkit `46971ac`, tagged v0.3.3). With auth fixed, this VITE_BASE bug is now the next thing blocking the password-reset flow on a freshly-provisioned project. See also the companion issue for an end-to-end test that would have caught this *before* deploy.
Author
Owner

Agent Evaluation

Root cause located (corrects/refines the original issue body):

The wiring chain is:

  1. scripts/new-project.sh writes frontend/.env.production with VITE_BASE=/projects/<NAME>/ (line 360-369). confirmed present in palibu.
  2. frontend/vite.config.ts calls defineAdminConfig({ root: __dirname }) from @uschuster/webapp-scaffold.
  3. defineAdminConfig (src/vite-config.ts:24) reads const base = process.env.VITE_BASE || '/';.

Step 3 is wrong. Vite reads .env.production and exposes its VITE_* variables as import.meta.env.VITE_* for the client bundle, but it does not copy them into process.env at config-file evaluation time. So defineAdminConfig always sees process.env.VITE_BASE === undefined regardless of what's in .env.production, and the base falls through to '/'. The bundle then references /assets/index-*.js instead of /projects/<NAME>/assets/index-*.js.

Feasibility: Trivial. One-line change in webapp-scaffold/src/vite-config.ts: switch defineAdminConfig (and defineGuestConfig) from a plain function to a function that uses Vite's loadEnv(mode, root, '') so VITE_BASE comes from the .env files instead of process.env.

Impact: High — the deployed-behind-prefix flow is broken end-to-end on every freshly-scaffolded project. Plain blank page, no diagnostic.

Effort: Small.

Recommendation: Accept.

Implementation plan

  1. Refactor defineAdminConfig / defineGuestConfig to return Vite's config-as-function form so they receive { mode }:
    import { defineConfig, loadEnv } from 'vite';
    export function defineAdminConfig(opts) {
      return defineConfig(({ mode }) => {
        const env = loadEnv(mode, opts.root, '');
        const base = env.VITE_BASE || process.env.VITE_BASE || '/';
        return { base, plugins: [react()], build: {  } };
      });
    }
    
    Keep process.env.VITE_BASE as a fallback so CI invocations that export the variable explicitly still work.
  2. Bump @uschuster/webapp-scaffold package version (semver patch — bug fix).
  3. In each downstream (palibu, fewo-webapp, webapp-template), bump the dep to the new version and rebuild. Verify the emitted HTML references /projects/<NAME>/assets/….
  4. The companion e2e test (#7) prevents regression.

No decision checkboxes — the fix shape is forced by the root cause.

## Agent Evaluation **Root cause located** (corrects/refines the original issue body): The wiring chain is: 1. `scripts/new-project.sh` writes `frontend/.env.production` with `VITE_BASE=/projects/<NAME>/` (line 360-369). ✅ confirmed present in palibu. 2. `frontend/vite.config.ts` calls `defineAdminConfig({ root: __dirname })` from `@uschuster/webapp-scaffold`. 3. `defineAdminConfig` (`src/vite-config.ts:24`) reads `const base = process.env.VITE_BASE || '/';`. **Step 3 is wrong.** Vite reads `.env.production` and exposes its `VITE_*` variables as `import.meta.env.VITE_*` for the *client* bundle, but it does **not** copy them into `process.env` at *config-file* evaluation time. So `defineAdminConfig` always sees `process.env.VITE_BASE === undefined` regardless of what's in `.env.production`, and the base falls through to `'/'`. The bundle then references `/assets/index-*.js` instead of `/projects/<NAME>/assets/index-*.js`. **Feasibility:** Trivial. One-line change in `webapp-scaffold/src/vite-config.ts`: switch `defineAdminConfig` (and `defineGuestConfig`) from a plain function to a function that uses Vite's `loadEnv(mode, root, '')` so VITE_BASE comes from the .env files instead of `process.env`. **Impact:** High — the deployed-behind-prefix flow is broken end-to-end on every freshly-scaffolded project. Plain blank page, no diagnostic. **Effort:** Small. **Recommendation:** Accept. ### Implementation plan 1. Refactor `defineAdminConfig` / `defineGuestConfig` to return Vite's config-as-function form so they receive `{ mode }`: ```ts import { defineConfig, loadEnv } from 'vite'; export function defineAdminConfig(opts) { return defineConfig(({ mode }) => { const env = loadEnv(mode, opts.root, ''); const base = env.VITE_BASE || process.env.VITE_BASE || '/'; return { base, plugins: [react()], build: { … } }; }); } ``` Keep `process.env.VITE_BASE` as a fallback so CI invocations that export the variable explicitly still work. 2. Bump `@uschuster/webapp-scaffold` package version (semver patch — bug fix). 3. In each downstream (palibu, fewo-webapp, webapp-template), bump the dep to the new version and rebuild. Verify the emitted HTML references `/projects/<NAME>/assets/…`. 4. The companion e2e test (#7) prevents regression. No decision checkboxes — the fix shape is forced by the root cause.
uwe.admin added the
evaluated
label 2026-04-25 21:09:12 +02:00
Author
Owner

Evaluated #6 — Small, recommend accept; root cause is process.env vs loadEnv mismatch in defineAdminConfig.

Evaluated #6 — Small, recommend accept; root cause is process.env vs loadEnv mismatch in defineAdminConfig.
uwe.admin added the
accepted
label 2026-04-25 21:32:07 +02:00
Author
Owner

Implemented in b1a13b8 — defineAdminConfig/defineGuestConfig now use Vite's loadEnv(mode, opts.root, '') so frontend/.env.production reaches the build. Bumped to 0.3.6. Downstream consumers (palibu, fewo-webapp, webapp-template) need a npm install @uschuster/webapp-scaffold@^0.3.6 and rebuild to pick it up — that piece is left for a separate sweep since it touches three repos.

Implemented in b1a13b8 — defineAdminConfig/defineGuestConfig now use Vite's `loadEnv(mode, opts.root, '')` so `frontend/.env.production` reaches the build. Bumped to 0.3.6. Downstream consumers (palibu, fewo-webapp, webapp-template) need a `npm install @uschuster/webapp-scaffold@^0.3.6` and rebuild to pick it up — that piece is left for a separate sweep since it touches three repos.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: uwe.admin/webapp-scaffold#6
No description provided.