Compare commits

...

2 commits

Author SHA1 Message Date
Adam Moussa
c75e76c05e
fix(ui): drive Vercel dashboard-API proxy through Nitro routeRules (#76)
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
The Vercel deploy 404'd at `/` because PR #75's `vercel-build` script
(`scripts/build-vercel-output.mjs`) ran `rm -rf .vercel/output` and rebuilt
it from `.output/public`. On Vercel CI, Nitro's Vercel preset auto-activates
(from the VERCEL env var) and emits the Build Output API layout to
`.vercel/output` itself during `vite build`; the script then clobbered that
correct output with a static-only config that could not resolve the SPA
`_shell.html` fallback or the server functions, so production returned
`404: NOT_FOUND` even though the build was READY.

Stop fighting Nitro and drive the proxy through it instead:

- Remove `scripts/build-vercel-output.mjs` and the `vercel-build` script;
  restore the plain `vite build` for both local and Vercel builds.
- Add an env-driven Nitro `routeRules` proxy in `vite.config.ts`. Nitro's
  Vercel preset compiles a plain external-URL `proxy` rule into a CDN-level
  rewrite in `.vercel/output/config.json` at build time, reading
  LANGGRAPH_BACKEND_URL (the per-project Vercel env var). Proxy, not redirect,
  so the osw_session cookie stays first-party (same-origin). The build fails
  loudly if LANGGRAPH_BACKEND_URL is missing on Vercel; the rule is omitted
  for plain local/off-Vercel builds (which use the E2E_HARNESS mock proxy).
- `vercel.json`: `buildCommand` back to `bun run build`; drop the
  `.output/public` outputDirectory so Vercel serves Nitro's `.vercel/output`.

Validated with `LANGGRAPH_BACKEND_URL=... NITRO_PRESET=vercel bun run build`:
the generated `.vercel/output/config.json` contains the
`/dashboard/api/(.*)` -> backend rewrite ahead of `handle: filesystem` and
the SPA catch-all, `_shell.html` and the 699 hashed assets are emitted, and
typecheck passes. Supersedes the broken approach in #75.
2026-06-30 12:06:32 -04:00
Adam Moussa
076e20d7e6
feat(ui): derive Vercel proxy backend from per-project env var (#75)
Generate the /dashboard/api/* proxy destination at build time from
LANGGRAPH_BACKEND_URL via the Vercel Build Output API, instead of hardcoding
it in ui/vercel.json. This lets the dev and main branches stay byte-identical
(required by the fast-forward-only prod promotion) while each Vercel project
proxies to its own LangGraph backend purely via its own env var — so both the
dev and prod dashboards can be git-linked.

- ui/vercel.json: drop the hardcoded rewrites; buildCommand -> bun run vercel-build
- ui/package.json: add vercel-build = vite build && node scripts/build-vercel-output.mjs
- ui/scripts/build-vercel-output.mjs: emit .vercel/output/config.json with
  [ proxy /dashboard/api/* -> $LANGGRAPH_BACKEND_URL, filesystem, SPA fallback ];
  throws if LANGGRAPH_BACKEND_URL is unset so a misconfig fails the build.

Keeps the app same-origin (no CORS/auth change); preserves the _shell.html SPA
fallback. Set LANGGRAPH_BACKEND_URL per Vercel project (All Environments).
2026-06-30 11:43:36 -04:00
2 changed files with 34 additions and 13 deletions

View file

@ -1,15 +1,5 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": null,
"buildCommand": "bun run build",
"outputDirectory": ".output/public",
"rewrites": [
{
"source": "/dashboard/api/:path*",
"destination": "https://open-swe-v3-f2834ffc0df05a46a10262a9690e8490.us.langgraph.app/dashboard/api/:path*"
},
{
"source": "/(.*)",
"destination": "/_shell.html"
}
]
"buildCommand": "bun run build"
}

View file

@ -74,13 +74,44 @@ function mockHarnessProxy(): Plugin | null {
}
}
// Same-origin proxy for the dashboard API. Vercel reads vercel.json BEFORE the build
// and never interpolates env into it, and prod promotes by fast-forward (so `dev` and
// `main` ship a byte-identical vercel.json that cannot encode a per-project backend).
// So the per-project backend is driven through Nitro instead: this `routeRules` proxy
// is compiled by Nitro's Vercel preset into a CDN-level rewrite in
// `.vercel/output/config.json` at build time, reading LANGGRAPH_BACKEND_URL from the
// per-project Vercel env var. Proxy (not redirect) keeps the osw_session cookie
// first-party on the Vercel origin. The `**` wildcard maps 1:1 to the destination.
function backendProxyRouteRules():
| Record<string, { proxy: string }>
| undefined {
const backend = process.env.LANGGRAPH_BACKEND_URL?.replace(/\/$/, "")
// Nitro's Vercel preset auto-activates from the VERCEL env var; NITRO_PRESET=vercel
// forces it locally. On Vercel the backend is mandatory — fail loudly rather than
// ship a build whose API calls 404, which is exactly how the previous approach broke.
const onVercel =
!!process.env.VERCEL || process.env.NITRO_PRESET === "vercel"
if (!backend) {
if (onVercel) {
throw new Error(
"LANGGRAPH_BACKEND_URL is required for Vercel builds (set it per Vercel " +
"project: dev project -> dev langgraph URL, prod project -> prod langgraph URL).",
)
}
// Off Vercel with no backend configured (plain local `vite build`, tests): omit the
// proxy. Local dev gets its API via the E2E_HARNESS mock proxy above instead.
return undefined
}
return { "/dashboard/api/**": { proxy: `${backend}/dashboard/api/**` } }
}
const config = defineConfig({
optimizeDeps: { include: ["workbox-window"] },
worker: { format: "es" },
plugins: [
mockHarnessProxy(),
devtools(),
nitro(),
nitro({ routeRules: backendProxyRouteRules() }),
viteTsConfigPaths({
projects: ["./tsconfig.json"],
}),