Skip to content

Commit a249322

Browse files
fix: follow hotcrm Vercel deployment pattern for API function detection
Adapts the studio's Vercel deployment to match the proven hotcrm pattern: 1. Created committed `api/[[...route]].ts` catch-all route so Vercel can detect it as a serverless function during pre-build phase. 2. Changed esbuild output format from CJS to ESM — the studio's package.json has "type": "module", which causes Node.js to parse .js files as ESM. The previous CJS output (require/module.exports) would fail to parse. 3. Changed esbuild output path from `api/index.js` to `api/[[...route]].js` matching the catch-all naming convention used by hotcrm. 4. Updated vercel.json rewrites from `/api/(.*)` → `/api` to `/api/:path*` → `/api/[[...route]]` matching the hotcrm pattern. 5. Removed `outputDirectory: "public"` — Vercel's default for framework:null already serves static from public/ and functions from api/ at project root. 6. Reverted build-vercel.sh to original (no copy-to-public/api step). Reference: https://github.com/objectstack-ai/hotcrm/blob/main/vercel.json Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/15dce836-8636-4011-9192-87275ec8c7ce Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent 15073a5 commit a249322

7 files changed

Lines changed: 48 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Fixed
11-
- **Studio Vercel API routes returning HTML instead of JSON** — Updated
12-
`apps/studio/scripts/build-vercel.sh` to copy the bundled serverless function
13-
(`api/index.js`) into the output directory (`public/api/`). Vercel's
14-
`framework: null` defaults `outputDirectory` to `public/` when that directory
15-
exists, and only detects serverless functions **inside** the output directory.
16-
Previously `api/index.js` was generated at the project root (outside `public/`)
17-
and Vercel never recognised it, causing all `/api/*` routes to fall through to
18-
the SPA catch-all rewrite. Also set `outputDirectory: "public"` explicitly in
19-
`vercel.json` for clarity, and removed the `functions` block (the in-code
20-
`export const config` already configures memory/maxDuration without pre-build
21-
file-pattern validation errors).
11+
- **Studio Vercel API routes returning HTML instead of JSON** — Adopted the
12+
same Vercel deployment pattern used by `hotcrm`: committed
13+
`api/[[...route]].ts` catch-all route so Vercel detects it pre-build,
14+
switched esbuild output from CJS to ESM (fixes `"type": "module"` conflict),
15+
and changed the output path from `api/index.js` to `api/[[...route]].js`.
16+
Updated rewrites to match: `/api/:path*``/api/[[...route]]`.
2217
- **Studio CORS error on Vercel temporary/preview domains** — Changed
2318
`VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""`
2419
(empty string / same-origin) in `vercel.json` so each deployment — including

apps/studio/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ pnpm-lock.yaml
55
# Build outputs
66
dist
77
build
8-
api
8+
api/*.js
9+
api/*.js.map
910
*.tsbuildinfo
1011

1112
# Development

apps/studio/api/[[...route]].ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* Vercel Serverless Function — Catch-all API route.
5+
*
6+
* This file MUST be committed to the repository so Vercel can detect it
7+
* as a serverless function during the pre-build phase.
8+
*
9+
* During the Vercel build step, `scripts/bundle-api.mjs` uses esbuild to
10+
* bundle `server/index.ts` (with all workspace dependencies inlined) and
11+
* outputs the result as `api/[[...route]].js`, which replaces this file
12+
* at deploy time. The esbuild bundle is used instead of Vercel's native
13+
* TypeScript compilation because pnpm strict mode (no shamefully-hoist)
14+
* prevents @vercel/node from resolving workspace:* packages correctly.
15+
*
16+
* @see {@link ../server/index.ts} — the actual server entrypoint
17+
* @see {@link ../scripts/bundle-api.mjs} — the esbuild bundler
18+
* @see {@link https://github.com/objectstack-ai/hotcrm/blob/main/vercel.json} — reference deployment
19+
*/
20+
21+
export { default, config } from '../server/index';

apps/studio/scripts/build-vercel.sh

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ set -euo pipefail
33

44
# Build script for Vercel deployment of @objectstack/studio.
55
#
6-
# Vercel uses outputDirectory (vercel.json → "public") for BOTH static files
7-
# and serverless function detection. The api/ subdirectory MUST be inside
8-
# the output directory — files at the project root are ignored.
6+
# Follows the same Vercel deployment pattern as hotcrm:
7+
# - api/[[...route]].ts is committed to the repo (Vercel detects it pre-build)
8+
# - esbuild bundles server/index.ts → api/[[...route]].js (replaces TS at deploy)
9+
# - Vite SPA output is copied to public/ for CDN serving
10+
#
11+
# Vercel routing (framework: null, no outputDirectory):
12+
# - Static files: served from public/
13+
# - Serverless functions: detected from api/ at project root
914
#
1015
# Steps:
1116
# 1. Turbo build (Vite SPA → dist/)
12-
# 2. Bundle the API serverless function (→ api/index.js)
17+
# 2. Bundle the API serverless function (→ api/[[...route]].js)
1318
# 3. Copy Vite output to public/ for Vercel CDN serving
14-
# 4. Copy API function into public/api/ so Vercel detects it
1519

1620
echo "[build-vercel] Starting studio build..."
1721

@@ -28,11 +32,4 @@ rm -rf public
2832
mkdir -p public
2933
cp -r dist/* public/
3034

31-
# 4. Copy API function into output directory for Vercel detection
32-
# Vercel only looks for serverless functions inside outputDirectory.
33-
# Without this step, api/index.js at the project root is invisible.
34-
mkdir -p public/api
35-
cp api/index.js public/api/
36-
cp api/index.js.map public/api/ 2>/dev/null || true
37-
38-
echo "[build-vercel] Done. Static files + API function in public/"
35+
echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js"

apps/studio/scripts/bundle-api.mjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ await build({
3535
entryPoints: ['server/index.ts'],
3636
bundle: true,
3737
platform: 'node',
38-
format: 'cjs',
38+
format: 'esm',
3939
target: 'es2020',
40-
outfile: 'api/index.js',
40+
outfile: 'api/[[...route]].js',
4141
sourcemap: true,
4242
external: EXTERNAL,
4343
// Silence warnings about optional/unused require() calls in knex drivers
4444
logOverride: { 'require-resolve-not-external': 'silent' },
45+
// Vercel resolves ESM .js files correctly when "type": "module" is set.
46+
// CJS format would conflict with the project's "type": "module" setting,
47+
// causing Node.js to fail parsing require()/module.exports as ESM syntax.
48+
banner: { js: '// Bundled by esbuild — see scripts/bundle-api.mjs' },
4549
});
4650

47-
console.log('[bundle-api] Bundled server/index.ts → api/index.js');
51+
console.log('[bundle-api] Bundled server/index.ts → api/[[...route]].js');

apps/studio/server/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ export default handle(app);
295295
/**
296296
* Vercel per-function configuration.
297297
*
298-
* Picked up by the @vercel/node runtime from the deployed api/index.js bundle.
298+
* Picked up by the @vercel/node runtime from the deployed api/[[...route]].js bundle.
299299
* Replaces the top-level "functions" key in vercel.json so there is no
300300
* pre-build file-pattern validation against a not-yet-bundled artifact.
301301
*/

apps/studio/vercel.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"framework": null,
44
"installCommand": "cd ../.. && pnpm install",
55
"buildCommand": "bash scripts/build-vercel.sh",
6-
"outputDirectory": "public",
76
"build": {
87
"env": {
98
"VITE_RUNTIME_MODE": "server",
@@ -19,7 +18,7 @@
1918
}
2019
],
2120
"rewrites": [
22-
{ "source": "/api/(.*)", "destination": "/api" },
21+
{ "source": "/api/:path*", "destination": "/api/[[...route]]" },
2322
{ "source": "/((?!api/).*)", "destination": "/index.html" }
2423
]
2524
}

0 commit comments

Comments
 (0)