Skip to content

Commit 854046a

Browse files
fix(studio): resolve router basepath at runtime so pre-built dist works under /_studio/
Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/8502c67c-765c-4170-acc4-8c27a49b32b9 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent 718cb69 commit 854046a

File tree

4 files changed

+72
-15
lines changed

4 files changed

+72
-15
lines changed

apps/studio/CHANGELOG.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@
88
(e.g. `/_studio/` via the CLI `--ui` flag). Previously, routes such as
99
`/_studio/packages` or `/_studio/:package/objects/:name` failed to match —
1010
the router treated the mount prefix as a `$package` route parameter, producing
11-
"Not Found" errors. The router now derives `basepath` from Vite's
12-
`import.meta.env.BASE_URL`, which works transparently for both root and
13-
sub-path deployments.
11+
"Not Found" errors on hard refresh and dropping the prefix on sidebar clicks
12+
(`navigate({ to: '/packages' })``/packages`).
13+
14+
The router now resolves its `basepath` from (1) a runtime global
15+
`window.__OBJECTSTACK_STUDIO_BASEPATH__` injected by the host server, falling
16+
back to (2) Vite's `import.meta.env.BASE_URL`, then (3) `'/'`. The runtime
17+
override is required because `import.meta.env.BASE_URL` is a build-time
18+
constant — a pre-built dist (shipped via npm and re-hosted by the CLI static
19+
plugin under `/_studio/`) would otherwise hard-code basepath as `'/'`
20+
regardless of its actual mount path. The CLI's `createStudioStaticPlugin` now
21+
injects the appropriate global so the same dist works at any mount point.
1422

1523
## 4.0.4
1624

apps/studio/src/router.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,49 @@ import { createRouter } from '@tanstack/react-router';
1111
import { routeTree } from './routeTree.gen';
1212

1313
/**
14-
* Compute the router basepath from Vite's `BASE_URL`.
14+
* Compute the router basepath for TanStack Router.
1515
*
16-
* When Studio is mounted under a sub-path (e.g. `/_studio/` via the CLI `--ui`
17-
* flag, which sets `VITE_BASE=/_studio/`), TanStack Router must strip that
18-
* prefix before matching route patterns. Otherwise URLs such as
19-
* `/_studio/packages` are mis-interpreted as `/$package="_studio"/packages`.
16+
* When Studio is mounted under a sub-path (e.g. `/_studio/`), TanStack Router
17+
* must strip that prefix before matching route patterns. Otherwise URLs such
18+
* as `/_studio/packages` are mis-interpreted as `/$package="_studio"/packages`.
2019
*
21-
* Vite exposes the configured base as `import.meta.env.BASE_URL`:
22-
* - Root deployment: `'/'` → basepath `'/'` (no-op)
23-
* - Sub-path deployment: `'/_studio/'` → basepath `'/_studio'`
20+
* Resolution order (first match wins):
21+
*
22+
* 1. **Runtime global `window.__OBJECTSTACK_STUDIO_BASEPATH__`** — injected
23+
* into `index.html` by the host server (see `createStudioStaticPlugin`
24+
* in `@objectstack/cli`) when the pre-built dist is served under a
25+
* sub-path. This is the authoritative signal for *any* deployment where
26+
* the same pre-built bundle is re-hosted at a different mount point —
27+
* `import.meta.env.BASE_URL` is a build-time constant and cannot
28+
* capture this.
29+
*
30+
* 2. **Vite `import.meta.env.BASE_URL`** — works at dev-server time when
31+
* `VITE_BASE` is set (e.g. the CLI dev proxy), and for bundles that
32+
* were explicitly built with a non-default `base` config.
33+
*
34+
* 3. Fallback: `'/'` (root deployment).
2435
*
2536
* TanStack Router expects the basepath WITHOUT a trailing slash (except for
2637
* the root `'/'`), so we normalise accordingly.
2738
*/
39+
function normalise(base: string): string {
40+
const trimmed = base.trim();
41+
if (!trimmed || trimmed === '/' || trimmed === './') return '/';
42+
return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed;
43+
}
44+
2845
function resolveBasepath(): string {
29-
const base = (import.meta.env.BASE_URL ?? '/').trim();
30-
if (!base || base === '/' || base === './') return '/';
31-
return base.endsWith('/') ? base.slice(0, -1) : base;
46+
// 1. Runtime injection from host server (works for any pre-built dist)
47+
if (typeof window !== 'undefined') {
48+
const injected = (window as unknown as { __OBJECTSTACK_STUDIO_BASEPATH__?: string })
49+
.__OBJECTSTACK_STUDIO_BASEPATH__;
50+
if (typeof injected === 'string' && injected.length > 0) {
51+
return normalise(injected);
52+
}
53+
}
54+
55+
// 2. Vite build-time / dev-server base
56+
return normalise(import.meta.env.BASE_URL ?? '/');
3257
}
3358

3459
export const router = createRouter({

packages/cli/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# @objectstack/cli
22

3+
## Unreleased
4+
5+
### Patch Changes
6+
7+
- `createStudioStaticPlugin` now injects
8+
`<script>window.__OBJECTSTACK_STUDIO_BASEPATH__="/_studio";</script>` into the
9+
rewritten `index.html`. This allows the Studio's TanStack Router to discover
10+
its mount path at runtime — a pre-built Studio `dist/` (whose
11+
`import.meta.env.BASE_URL` is baked in as `'./'` at build time) previously
12+
hard-coded its router basepath to `'/'`, breaking deep-link refresh and
13+
causing sidebar navigation to drop the `/_studio` prefix.
14+
315
## 4.0.4
416

517
### Patch Changes

packages/cli/src/utils/studio.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,23 @@ export function createStudioStaticPlugin(distPath: string, options?: { isDev?: b
284284
// Read and rewrite index.html so asset paths respect the mount path.
285285
// The dist may have been built with base '/' (absolute paths like
286286
// /assets/...) which won't resolve when mounted under /_studio/.
287+
//
288+
// We also inject a tiny inline script that publishes the runtime
289+
// basepath as `window.__OBJECTSTACK_STUDIO_BASEPATH__`. The Studio's
290+
// TanStack Router reads this at boot to configure its `basepath` —
291+
// without it, a pre-built bundle (whose `import.meta.env.BASE_URL`
292+
// is baked in at build time) would treat the mount prefix as a
293+
// route parameter and fail to resolve sub-routes.
287294
const rawHtml = fs.readFileSync(indexPath, 'utf-8');
288-
const rewrittenHtml = rawHtml.replace(
295+
const withRewrittenUrls = rawHtml.replace(
289296
/(\s(?:href|src))="\/(?!\/)/g,
290297
`$1="${STUDIO_PATH}/`,
291298
);
299+
const basepathScript =
300+
`<script>window.__OBJECTSTACK_STUDIO_BASEPATH__=${JSON.stringify(STUDIO_PATH)};</script>`;
301+
const rewrittenHtml = withRewrittenUrls.includes('</head>')
302+
? withRewrittenUrls.replace('</head>', `${basepathScript}</head>`)
303+
: `${basepathScript}${withRewrittenUrls}`;
292304

293305
// In dev mode, redirect root to Studio for convenience
294306
if (options?.isDev) {

0 commit comments

Comments
 (0)