Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions apps/server/scripts/build-vercel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ set -euo pipefail
# - api/[[...route]].js is committed to the repo (Vercel detects it pre-build)
# - esbuild bundles server/index.ts → api/_handler.js (self-contained bundle)
# - The committed .js wrapper re-exports from _handler.js at runtime
# - Studio SPA is built and copied to public/ for serving the UI
# - Studio SPA is built and copied to api/_studio/ for serving at /_studio
# - External dependencies installed in api/node_modules/ (no symlinks)
#
# Steps:
# 1. Build the project with turbo (includes studio)
# 2. Bundle the API serverless function (→ api/_handler.js)
# 3. Copy studio dist files to public/ for UI serving
# 3. Copy studio dist files to api/_studio/ for UI serving at /_studio
# 4. Install external deps in api/node_modules/ (resolve pnpm symlinks)

echo "[build-vercel] Starting server build..."
Expand All @@ -27,13 +27,13 @@ cd apps/server
# 2. Bundle API serverless function
node scripts/bundle-api.mjs

# 3. Copy studio dist files to public/ for UI serving
echo "[build-vercel] Copying studio dist to public/..."
rm -rf public
mkdir -p public
# 3. Copy studio dist files to api/_studio/ for UI serving at /_studio
echo "[build-vercel] Copying studio dist to api/_studio/..."
rm -rf api/_studio
mkdir -p api/_studio
if [ -d "../studio/dist" ]; then
cp -r ../studio/dist/* public/
echo "[build-vercel] ✓ Copied studio dist to public/"
cp -r ../studio/dist/* api/_studio/
echo "[build-vercel] ✓ Copied studio dist to api/_studio/"
else
echo "[build-vercel] ⚠ Studio dist not found (skipped)"
fi
Expand All @@ -60,4 +60,4 @@ rm package.json
cd ..
echo "[build-vercel] ✓ External dependencies installed in api/node_modules/"

echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js → api/_handler.js"
echo "[build-vercel] Done. Studio files in api/_studio/, serverless function in api/[[...route]].js → api/_handler.js"
30 changes: 30 additions & 0 deletions apps/server/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
import { ObjectKernel } from '@objectstack/runtime';
import { createHonoApp } from '@objectstack/hono';
import { getRequestListener } from '@hono/node-server';
import { serveStatic } from '@hono/node-server/serve-static';
import type { Hono } from 'hono';
import stackConfig from '../objectstack.config';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

// ---------------------------------------------------------------------------
// Singleton state — persists across warm Vercel invocations
Expand Down Expand Up @@ -69,6 +72,33 @@

const kernel = await ensureKernel();
_app = createHonoApp({ kernel, prefix: '/api/v1' });

// Serve studio at /_studio
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const studioPath = join(__dirname, '_studio');

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable studioPath.

// Serve static files from /_studio
_app.get('/_studio/*', serveStatic({
root: __dirname,
rewriteRequestPath: (path) => {
// Rewrite /_studio/assets/x.js -> /_studio/assets/x.js
return path;
}
}));

// SPA fallback for studio
_app.get('/_studio/*', serveStatic({
root: __dirname,
rewriteRequestPath: () => '/_studio/index.html'
}));

// Serve studio index at /_studio root
_app.get('/_studio', serveStatic({
root: __dirname,
rewriteRequestPath: () => '/_studio/index.html'
}));

return _app;
}

Expand Down
5 changes: 2 additions & 3 deletions apps/server/vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"functions": {
"api/**/*.js": {
"maxDuration": 60,
"includeFiles": "api/node_modules/**"
"includeFiles": "api/{node_modules,_studio}/**"
}
},
"headers": [
Expand All @@ -24,7 +24,6 @@
}
],
"rewrites": [
{ "source": "/api/:path*", "destination": "/api/[[...route]]" },
{ "source": "/((?!api/).*)", "destination": "/index.html" }
{ "source": "/api/:path*", "destination": "/api/[[...route]]" }
]
}
Loading