ObjectStackObjectStack

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.

ModeRuntimeVercel FeatureUse Case
Server (default)Node.js / EdgeServerless FunctionsProduction apps, Studio, real database
MSWBrowser (Service Worker)Static SiteOffline-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:

VariableModeDescription
VITE_RUNTIME_MODEMSWSet to msw (build-time Vite env)
VITE_SERVER_URLServerBackend API URL (empty for same-origin)
DATABASE_URLServerDatabase 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 backend

This is controlled by the config module which checks (in priority order):

  1. ?mode= URL parameter
  2. VITE_RUNTIME_MODE environment variable
  3. Embedded detection (/_studio/ path)
  4. Default: msw

Deployment Checklist

  • api/index.ts Hono entrypoint exists with handle(app) export
  • api/_kernel.ts boots the kernel with the correct driver and broker shim
  • vercel.json sets VITE_RUNTIME_MODE=server and VITE_SERVER_URL= (empty)
  • Rewrite rule routes /api/* to /api and excludes /api/ from SPA rewrite
  • DATABASE_URL is 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 --save has been run (Service Worker in public/)
  • vercel.json specifies "framework": "vite" and SPA rewrites
  • VITE_RUNTIME_MODE=msw is set in build environment
  • Seed data is defined in objectstack.config.ts (data array)

Comparison

FeatureMSW ModeServer Mode
DatabaseIn-memory (browser)PostgreSQL, MongoDB, etc.
Data PersistencePer session (lost on refresh)Persistent
Cold StartNone (client-side)~200ms (Serverless)
Offline Support✅ Full❌ Requires network
Multi-user❌ Single user✅ Full
CostFree (static hosting)Pay per invocation
Best ForDemos, prototypes, docsProduction applications

On this page