From 9e9a07504c1cb85ef5f16fa8b1d6935db8a9fa05 Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 25 Jun 2026 11:18:51 -0700 Subject: [PATCH 1/2] fix(web): drop the invalid hosted /s/ rewrite (basePath build break) The dashboard next.config emitted a /s/:path* rewrite under basePath="/app" whose destination (`${APP_BASE_PATH}/s/:path*` with basePath:false) is rejected by Next.js -- "rewrites urls outside of the basePath. Please use a destination that starts with http(s)://" -- breaking every hosted (basePath) build. It only triggers when NEXT_PUBLIC_BASE_PATH is set, so the OSS CI never hit it. It was also ineffective: on a hosted host the apex `/s/*` URL reaches the LANDING, not this basePath="/app" dashboard, so `/s/*` must be rewritten into `/app/s/*` at the apex (mirroring how `/app/*` is served), not self-rewritten here. OSS (no basePath) serves /s/[token] directly with no rewrite. Verified `next build` with NEXT_PUBLIC_BASE_PATH=/app now compiles (previously failed at config validation with "Invalid rewrite found"). Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/web/next.config.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 911e3e8c4..a9fdad9df 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -176,16 +176,14 @@ const nextConfig: NextConfig = { source: "/c/:token", destination: `${apiBase}/c/:token`, }, - // Hosted dashboard builds use basePath="/app". Public share links must - // remain top-level no-auth URLs, so /s/* is rewritten into the app route - // without changing the browser URL. - ...(APP_BASE_PATH - ? [{ - source: "/s/:path*", - destination: `${APP_BASE_PATH}/s/:path*`, - basePath: false as const, - }] - : []), + // /s/* public share links: NOT rewritten here. A dashboard self-rewrite of + // a top-level path into the basePath is rejected by Next.js ("rewrites urls + // outside of the basePath. Please use a destination that starts with + // http(s)://") and broke hosted (basePath="/app") builds. It was also + // ineffective: on the hosted host the apex URL `/s/*` reaches the landing, + // not this dashboard, so the apex is where `/s/*` must be rewritten into + // `/app/s/*` (mirroring how `/app/*` is served). OSS (no basePath) serves + // /s/[token] directly with no rewrite. ]; }, async headers() { From 1bd89e6ab09078b93a90281ddac951e70b996b93 Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 25 Jun 2026 11:25:25 -0700 Subject: [PATCH 2/2] test(web): assert the dashboard config does NOT self-rewrite /s/* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prior test enforced the now-removed /s/ rewrite (the one that broke hosted basePath builds). Flip it to assert next.config.ts no longer contains the /s/ rewrite — /s/* is handled at the hosted apex (-> /app/s/*), OSS serves /s/[token] directly. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/web/tests/approval-batch-share-public.test.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/web/tests/approval-batch-share-public.test.ts b/apps/web/tests/approval-batch-share-public.test.ts index e97bb7b17..6054c53ca 100644 --- a/apps/web/tests/approval-batch-share-public.test.ts +++ b/apps/web/tests/approval-batch-share-public.test.ts @@ -139,11 +139,16 @@ describe("public approval batch share route", () => { expect(fetchMock).not.toHaveBeenCalled(); }); - it("keeps minted /s links top-level when the hosted app uses basePath /app", () => { + it("does NOT self-rewrite /s/* in the dashboard config (apex handles it)", () => { + // A dashboard self-rewrite of top-level /s/* into the basePath is rejected by + // Next.js ("rewrites urls outside of the basePath") and broke hosted + // (basePath="/app") builds. It was also ineffective: the hosted /s/* URL + // reaches the landing apex, not this dashboard, so the apex rewrites + // /s/* -> /app/s/* (mirroring /app/*). OSS (no basePath) serves /s/[token] + // directly. So next.config must NOT contain the /s/ rewrite. const config = readFileSync(join(process.cwd(), "next.config.ts"), "utf-8"); - expect(config).toContain('source: "/s/:path*"'); - expect(config).toContain("destination: `${APP_BASE_PATH}/s/:path*`"); - expect(config).toContain("basePath: false"); + expect(config).not.toContain('source: "/s/:path*"'); + expect(config).not.toContain("destination: `${APP_BASE_PATH}/s/:path*`"); }); it("does not call API_BASE directly from the logged-out share card", () => {