Deploy to Vercel
Deploy ObjectStack applications to Vercel — Server mode (recommended) for real API endpoints or MSW mode for static demos
Deploy to Vercel
ObjectStack supports two deployment modes on Vercel. Server mode is recommended for production and is the default for ObjectStack Studio.
| Mode | Runtime | Vercel Feature | Use Case |
|---|---|---|---|
| Server (default) | Node.js / Edge | Serverless Functions | Production apps, Studio, real database |
| MSW | Browser (Service Worker) | Static Site | Offline-only demos, prototypes |
MSW Mode (Static SPA)
In MSW mode the entire ObjectStack kernel runs in the browser. Mock Service Worker intercepts fetch calls and routes them to an in-memory ObjectQL engine — no backend required.
How It Works
┌─────────────────────────── Browser ───────────────────────────┐
│ React App → fetch('/api/v1/data/task') │
│ ↓ │
│ MSW Service Worker (intercepts request) │
│ ↓ │
│ ObjectKernel → ObjectQL → InMemoryDriver │
│ ↓ │
│ JSON Response → React App │
└───────────────────────────────────────────────────────────────┘Project Setup
// objectstack.config.ts
import { defineStack } from '@objectstack/spec';
import * as objects from './src/objects';
export default defineStack({
manifest: {
id: 'com.example.myapp',
name: 'My App',
version: '1.0.0',
type: 'app',
},
objects: Object.values(objects),
data: [
{
object: 'task',
mode: 'upsert',
externalId: 'subject',
records: [
{ subject: 'Learn ObjectStack', status: 'in_progress', priority: 'high' },
],
},
],
});Kernel Bootstrap (Browser)
Create a kernel factory that boots the entire stack in the browser:
// src/mocks/createKernel.ts
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { MSWPlugin } from '@objectstack/plugin-msw';
export async function createKernel(appConfigs: any[]) {
const kernel = new ObjectKernel();
await kernel.use(new ObjectQLPlugin());
await kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory'));
for (const config of appConfigs) {
await kernel.use(new AppPlugin(config));
}
await kernel.use(new MSWPlugin({
enableBrowser: true,
baseUrl: '/api/v1',
logRequests: true,
}));
await kernel.bootstrap();
return kernel;
}Entry Point
// src/main.tsx
import appConfig from '../objectstack.config';
import { createKernel } from './mocks/createKernel';
async function bootstrap() {
// Boot the in-browser kernel before rendering
await createKernel([appConfig]);
// Now render — all fetch('/api/v1/...') calls are intercepted by MSW
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
}
bootstrap();Vite Configuration
MSW requires the Service Worker file in your public/ directory. Add the init script and configure Vite:
# Generate the MSW Service Worker file
npx msw init public --save// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
optimizeDeps: {
include: ['msw', 'msw/browser', '@objectstack/spec'],
},
});vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": "vite",
"buildCommand": "vite build",
"outputDirectory": "dist",
"build": {
"env": {
"VITE_RUNTIME_MODE": "msw"
}
},
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}The rewrites rule is essential for SPA routing — it ensures all paths serve index.html so the client-side router can handle navigation.
Monorepo Configuration
If your project lives in a monorepo (e.g. pnpm workspaces + Turborepo), update the install and build commands:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": "vite",
"installCommand": "cd ../.. && pnpm install",
"buildCommand": "cd ../.. && pnpm turbo run build --filter=@myorg/my-app",
"outputDirectory": "dist",
"build": {
"env": {
"VITE_RUNTIME_MODE": "msw"
}
},
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}Set the Root Directory in Vercel project settings to the app's folder (e.g. apps/my-app).
Server Mode (Serverless Functions)
In Server mode, ObjectStack runs inside Vercel Serverless Functions. API requests are handled by real backend logic with a real database.
Architecture
┌─── Vercel ────────────────────────────────────────────────────┐
│ │
│ Static Assets (React SPA) │
│ ┌───────────────────────────────────┐ │
│ │ /index.html, /assets/* │ │
│ └───────────────────────────────────┘ │
│ │
│ Serverless Functions │
│ ┌───────────────────────────────────┐ │
│ │ /api/[...objectstack] │ │
│ │ → ObjectKernel │ │
│ │ → ObjectQL │ │
│ │ → PostgreSQL / MongoDB / ... │──── External DB │
│ └───────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────┘Option A: Next.js + @objectstack/nextjs
This is the recommended approach for Vercel. The @objectstack/nextjs adapter maps all ObjectStack protocol endpoints to a single Next.js catch-all route.
1. Create the kernel singleton:
// lib/kernel.ts
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import appConfig from '../objectstack.config';
let kernel: ObjectKernel | null = null;
export async function getKernel() {
if (kernel) return kernel;
kernel = new ObjectKernel();
await kernel.use(new ObjectQLPlugin());
// Use your production driver (Postgres, MongoDB, etc.)
// await kernel.use(new DriverPlugin(new PostgresDriver({
// url: process.env.DATABASE_URL,
// })));
// Load the application configuration (objects, data, etc.)
await kernel.use(new AppPlugin(appConfig));
await kernel.bootstrap();
return kernel;
}2. Create the API route handler:
// app/api/[...objectstack]/route.ts
import { createRouteHandler } from '@objectstack/nextjs';
import { getKernel } from '@/lib/kernel';
async function handler(...args: any[]) {
const kernel = await getKernel();
const routeHandler = createRouteHandler({ kernel, prefix: '/api' });
return routeHandler(...args);
}
export { handler as GET, handler as POST, handler as PATCH, handler as DELETE };3. vercel.json (optional — Next.js works out of the box):
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": "nextjs"
}Option B: Hono + @objectstack/hono (Vite SPA)
This is what ObjectStack Studio uses. The Vite SPA is served as static assets, and a Hono-based serverless function handles /api/* requests.
1. Create the kernel singleton (api/_kernel.ts — prefixed with _ to prevent Vercel from creating a route):
// api/_kernel.ts
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { createHonoApp } from '@objectstack/hono';
import { Hono } from 'hono';
import appConfig from '../objectstack.config';
let _kernel: ObjectKernel | null = null;
let _app: Hono | null = null;
async function ensureKernel(): Promise<ObjectKernel> {
if (_kernel) return _kernel;
_kernel = new ObjectKernel();
await _kernel.use(new ObjectQLPlugin());
await _kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory'));
await _kernel.use(new AppPlugin(appConfig));
await _kernel.bootstrap();
return _kernel;
}
export async function ensureApp(): Promise<Hono> {
if (_app) return _app;
const kernel = await ensureKernel();
_app = createHonoApp({ kernel, prefix: '/api/v1' });
return _app;
}2. Create the API entrypoint (api/index.ts):
// api/index.ts
import { Hono } from 'hono';
import { handle } from 'hono/vercel';
import { ensureApp } from './_kernel';
const app = new Hono();
app.all('*', async (c) => {
const inner = await ensureApp();
return inner.fetch(c.req.raw);
});
export default handle(app);3. vercel.json:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": "vite",
"buildCommand": "vite build",
"outputDirectory": "dist",
"build": {
"env": {
"VITE_RUNTIME_MODE": "server",
"VITE_SERVER_URL": ""
}
},
"rewrites": [
{ "source": "/api/(.*)", "destination": "/api" },
{ "source": "/((?!api/).*)", "destination": "/index.html" }
]
}Setting VITE_SERVER_URL to empty string tells the client SDK to use same-origin API calls. The first rewrite routes all /api/* sub-paths to the api/index.ts serverless function. The second rewrite excludes /api/ paths so all other paths serve the SPA.
Environment Variables
Configure these in Vercel Project Settings → Environment Variables:
| Variable | Mode | Description |
|---|---|---|
VITE_RUNTIME_MODE | MSW | Set to msw (build-time Vite env) |
VITE_SERVER_URL | Server | Backend API URL (empty for same-origin) |
DATABASE_URL | Server | Database connection string |
Never commit secrets. Use Vercel's environment variable UI or the Vercel CLI (vercel env add) to configure DATABASE_URL and other credentials.
Runtime Mode Switching
ObjectStack Studio supports switching between MSW and Server mode at runtime using a URL parameter:
https://myapp.vercel.app?mode=msw → In-browser kernel
https://myapp.vercel.app?mode=server → Real backendThis is controlled by the config module which checks (in priority order):
?mode=URL parameterVITE_RUNTIME_MODEenvironment variable- Embedded detection (
/_studio/path) - Default:
msw
Deployment Checklist
Server Mode (Recommended)
-
api/index.tsHono entrypoint exists withhandle(app)export -
api/_kernel.tsboots the kernel with the correct driver and broker shim -
vercel.jsonsetsVITE_RUNTIME_MODE=serverandVITE_SERVER_URL=(empty) - Rewrite rule routes
/api/*to/apiand excludes/api/from SPA rewrite -
DATABASE_URLis configured in Vercel environment variables (for production drivers) - CORS is configured if frontend and API are on different origins
MSW Mode (Static Demo)
-
msw init public --savehas been run (Service Worker inpublic/) -
vercel.jsonspecifies"framework": "vite"and SPA rewrites -
VITE_RUNTIME_MODE=mswis set in build environment - Seed data is defined in
objectstack.config.ts(dataarray)
Comparison
| Feature | MSW Mode | Server Mode |
|---|---|---|
| Database | In-memory (browser) | PostgreSQL, MongoDB, etc. |
| Data Persistence | Per session (lost on refresh) | Persistent |
| Cold Start | None (client-side) | ~200ms (Serverless) |
| Offline Support | ✅ Full | ❌ Requires network |
| Multi-user | ❌ Single user | ✅ Full |
| Cost | Free (static hosting) | Pay per invocation |
| Best For | Demos, prototypes, docs | Production applications |
Related
- Plugin System — MSW and Hono server plugins
- Client SDK — Frontend data fetching
- Driver Configuration — Database setup
- Architecture — Kernel and runtime overview