fix(ui): dev server 404s on migrated-page links because uiBase hardcodes /ui (#30169)

* fix(ui): serve migrated-page links unprefixed on the dev server

migratedHref and legacyPageHref always prepended /ui, which is where the
proxy mounts the static export but not where next dev serves the app
(basePath is empty; the app lives at the root on localhost:3000). Every
sidebar link to a migrated page and every ?page= bookmark redirect
therefore 404'd in dev, and would do so for each page cut over in the
App Router migration.

uiBase now returns the bare root under NODE_ENV=development. The check
is inlined at build time, so production output is unchanged for both
the default /ui mount and server_root_path deployments.

* test(ui): pin NODE_ENV in production-mode migratedPages tests

The production-mode describes relied on vitest defaulting NODE_ENV to
test; a developer with NODE_ENV=development exported in their shell
would see them fail. Stub it explicitly so the suite is deterministic
regardless of ambient environment.
This commit is contained in:
ryan-crabbe-berri 2026-06-10 17:16:36 -07:00 committed by GitHub
parent da9d64b4de
commit 496f5b9859
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 1 deletions

View File

@ -1,8 +1,13 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
describe("migratedHref / legacyPageHref", () => {
beforeEach(() => {
vi.resetModules();
vi.stubEnv("NODE_ENV", "test");
});
afterEach(() => {
vi.unstubAllEnvs();
});
it("builds a /ui-rooted path when serverRootPath is /", async () => {
@ -37,9 +42,48 @@ describe("migratedHref / legacyPageHref", () => {
});
});
describe("dev server (NODE_ENV=development)", () => {
beforeEach(() => {
vi.resetModules();
vi.stubEnv("NODE_ENV", "development");
});
afterEach(() => {
vi.unstubAllEnvs();
});
it("builds root-relative hrefs because next dev serves the app at /, not /ui", async () => {
vi.doMock("@/components/networking", () => ({ serverRootPath: "/" }));
const { migratedHref, legacyPageHref } = await import("./migratedPages");
expect(migratedHref("api-reference")).toBe("/api-reference");
expect(legacyPageHref("models")).toBe("/?page=models");
});
it("ignores serverRootPath, which only applies to proxy-mounted deployments", async () => {
vi.doMock("@/components/networking", () => ({ serverRootPath: "/team-x/" }));
const { migratedHref } = await import("./migratedPages");
expect(migratedHref("api-reference")).toBe("/api-reference");
});
it("maps a bare migrated path back to its legacy sidebar key", async () => {
vi.doMock("@/components/networking", () => ({ serverRootPath: "/" }));
const { legacyKeyForPathname } = await import("./migratedPages");
expect(legacyKeyForPathname("/api-reference/")).toBe("api_ref");
expect(legacyKeyForPathname("/")).toBeNull();
});
});
describe("legacyKeyForPathname", () => {
beforeEach(() => {
vi.resetModules();
vi.stubEnv("NODE_ENV", "test");
});
afterEach(() => {
vi.unstubAllEnvs();
});
it("maps a migrated path back to its legacy sidebar key (including trailing slash)", async () => {

View File

@ -15,6 +15,11 @@ export const MIGRATED_PAGES: Record<string, string> = {
};
function uiBase(): string {
// next dev serves the app at the root; only the proxy mounts the static export under /ui
// (and optionally under server_root_path). Inlined at build time, so production is unaffected.
if (process.env.NODE_ENV === "development") {
return "";
}
const root = serverRootPath && serverRootPath !== "/" ? `/${serverRootPath.replace(/^\/+|\/+$/g, "")}` : "";
return `${root}/ui`;
}