Skip to content

Commit e95f3a9

Browse files
fix: add CORS middleware for Vercel temporary/preview domain deployments
Add hono/cors middleware to the outer Hono app in server/index.ts so the serverless function returns proper Access-Control-Allow-Origin headers for cross-origin requests from Vercel preview/temporary domains. Allowed origins: - All Vercel deployment URLs from VERCEL_URL, VERCEL_BRANCH_URL, VERCEL_PROJECT_PRODUCTION_URL env vars - Any *.vercel.app subdomain (covers all preview deployments) - localhost for local development The CORS middleware is placed before the catch-all route so OPTIONS preflight requests are handled immediately without kernel boot latency. Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/324fd769-65e6-430a-aab8-06ad343e75f2 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent 8805506 commit e95f3a9

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
correct directory, declared `api/index.js` in the `functions` block so Vercel
1515
recognises it as a serverless function, and changed the API rewrite destination
1616
from `/api` to `/api/index.js` to route requests to the bundled handler.
17+
- **Studio CORS error on Vercel temporary/preview domains** — Added Hono CORS
18+
middleware to `apps/studio/server/index.ts` so the serverless function returns
19+
correct `Access-Control-Allow-Origin` headers for cross-origin requests.
20+
Dynamically allows all `*.vercel.app` subdomains, explicitly listed Vercel
21+
deployment URLs (`VERCEL_URL`, `VERCEL_BRANCH_URL`,
22+
`VERCEL_PROJECT_PRODUCTION_URL`), and localhost for development. Preflight
23+
(OPTIONS) requests are answered immediately without waiting for kernel boot.
1724
- **Client test aligned with removed `ai.chat` method** — Updated
1825
`@objectstack/client` test suite to match the current API surface where
1926
`ai.chat()` was removed in favour of the Vercel AI SDK `useChat()` hook.

apps/studio/server/index.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { MetadataPlugin } from '@objectstack/metadata';
3636
import { AIServicePlugin } from '@objectstack/service-ai';
3737
import { handle } from '@hono/node-server/vercel';
3838
import { Hono } from 'hono';
39+
import { cors } from 'hono/cors';
3940
import { createBrokerShim } from '../src/lib/create-broker-shim.js';
4041
import studioConfig from '../objectstack.config.js';
4142

@@ -218,6 +219,50 @@ async function ensureApp(): Promise<Hono> {
218219
*/
219220
const app = new Hono();
220221

222+
// ---------------------------------------------------------------------------
223+
// CORS middleware
224+
// ---------------------------------------------------------------------------
225+
// Placed on the outer app so preflight (OPTIONS) requests are answered
226+
// immediately, without waiting for the kernel cold-start. This is essential
227+
// when the SPA is loaded from a Vercel temporary/preview domain but the
228+
// API base URL points to a different deployment (cross-origin).
229+
//
230+
// Allowed origins:
231+
// 1. All Vercel deployment URLs exposed via env vars (current deployment)
232+
// 2. Any *.vercel.app subdomain (covers all preview/branch deployments)
233+
// 3. localhost (local development)
234+
// ---------------------------------------------------------------------------
235+
236+
const vercelOrigins: string[] = [];
237+
if (process.env.VERCEL_URL) {
238+
vercelOrigins.push(`https://${process.env.VERCEL_URL}`);
239+
}
240+
if (process.env.VERCEL_BRANCH_URL) {
241+
vercelOrigins.push(`https://${process.env.VERCEL_BRANCH_URL}`);
242+
}
243+
if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {
244+
vercelOrigins.push(`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`);
245+
}
246+
247+
app.use('*', cors({
248+
origin: (origin) => {
249+
// Same-origin or non-browser requests (no Origin header)
250+
if (!origin) return origin;
251+
// Explicitly listed Vercel deployment origins
252+
if (vercelOrigins.includes(origin)) return origin;
253+
// Any *.vercel.app subdomain (preview / temp deployments)
254+
if (origin.endsWith('.vercel.app') && origin.startsWith('https://')) return origin;
255+
// Localhost for development
256+
if (/^https?:\/\/localhost(:\d+)?$/.test(origin)) return origin;
257+
// Deny — return empty string so no Access-Control-Allow-Origin is set
258+
return '';
259+
},
260+
credentials: true,
261+
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
262+
allowHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
263+
maxAge: 86400,
264+
}));
265+
221266
app.all('*', async (c) => {
222267
console.log(`[Vercel] ${c.req.method} ${c.req.url}`);
223268

0 commit comments

Comments
 (0)