// Repackage the static Vite/TanStack-Start build (.output/public) into the Vercel // Build Output API layout (.vercel/output), generating the /dashboard/api/* proxy // destination from a per-project env var. // // Why this exists: prod deploys via a fast-forward-only promotion, so the `dev` and // `main` branches are byte-identical and a committed vercel.json CANNOT differ between // them. The app is same-origin only (no backend CORS), so the per-environment backend // must be the PROXY DESTINATION, supplied per Vercel project as LANGGRAPH_BACKEND_URL. // Vercel reads vercel.json BEFORE the build and never interpolates env into it, so the // routes are emitted here (read AFTER the build) where process.env is available. import { cp, mkdir, writeFile, rm } from "node:fs/promises" const backend = process.env.LANGGRAPH_BACKEND_URL if (!backend) { throw new Error( "LANGGRAPH_BACKEND_URL is required at build time (set it per Vercel project: " + "dev project -> dev langgraph URL, prod project -> prod langgraph URL).", ) } const base = backend.replace(/\/$/, "") await rm(".vercel/output", { recursive: true, force: true }) await mkdir(".vercel/output/static", { recursive: true }) await cp(".output/public", ".vercel/output/static", { recursive: true }) await writeFile( ".vercel/output/config.json", JSON.stringify( { version: 3, routes: [ // Same-origin proxy to this project's backend (proxy, not redirect, so the // osw_session cookie stays first-party on the Vercel origin). { src: "^/dashboard/api/(.*)$", dest: `${base}/dashboard/api/$1` }, // Serve hashed assets, the PWA manifest/service worker, and _shell.html itself // BEFORE the SPA catch-all, or static files get swallowed by the fallback. { handle: "filesystem" }, // SPA fallback (replaces the old vercel.json /(.*) -> /_shell.html rewrite). { src: "^/.*$", dest: "/_shell.html" }, ], }, null, 2, ) + "\n", ) console.log(`[build-vercel-output] wrote .vercel/output/config.json (backend: ${base})`)