Skip to main content

Deploy to Vercel

This guide covers deploying Avocado Studio to Vercel.

Overview

The recommended deployment splits across two platforms:
  • Site + the editor on Vercel (static/serverless hosting)
  • Orchestrator on a long-running host (Render, Fly.io, Railway, or any container platform)
Two deployment phases are supported: public site only (Phase 1) or full the editor stack (Phase 2).

What Phase 1 already gives you

These three shipped and need no work on your part:
  1. apps/site renders published content from a committed lib/published-content.json when ORCHESTRATOR_URL is unset — no orchestrator needed at runtime.
  2. Draft fetches use Promise.allSettled, so an unreachable orchestrator degrades to published content instead of failing the render.
  3. __editor=1 authorizes nothing in production; a valid draft secret or a draft-mode cookie is required.
For Phase 1 you deploy only apps/site as a single Vercel project.

Current architecture (from this repo)

  • apps/site is a Next.js app.
  • apps/orchestrator is a Fastify API that persists session state to a local SQLite database via better-sqlite3 (.data/orchestrator.db + WAL).
  • apps/editor is a Vite app. Origins come from window.__AVOCADO_CONFIG__, then VITE_ORCHESTRATOR_URL / VITE_SITE_ORIGIN, with localhost only as a last resort.
  • The site currently reads draft content from ORCHESTRATOR_URL (/draft/pages, /draft/slugs).

Important decision first

Pick one deployment target:
  1. Public website only (recommended for launch)
  2. Full the editor + orchestration stack on Vercel

Use this if you want a stable public site and do not need live editing in production.

Required changes

  1. Decouple apps/site from draft API at runtime.
  • Today, apps/site fetches draft pages from orchestrator.
  • For production, render from a published source (shared seed content, JSON, CMS, or database).
  • Keep orchestrator integration optional (for dev/editor mode only).
  1. Add a published content source for site rendering.
  • Option A: Use demoPublishedPages() from packages/shared as initial published content.
  • Option B: Load from a real storage backend (recommended long term).
  1. Add production fallback behavior.
  • If orchestrator is unavailable, site should still render published pages (not Page not found).
  1. Restrict or disable editor mode in production.
  • __editor=1 should be disabled or protected.
  • Avoid exposing editor bridge behavior publicly by default.

Vercel config

Create one Vercel project for apps/site:
  • Root Directory: apps/site
  • Install Command: pnpm install --frozen-lockfile
  • Build Command: pnpm --filter @ai-site-editor/site build
  • Output: Next.js default

Environment variables (site)

  • ORCHESTRATOR_URL (optional in public-only mode; required only if you keep remote draft fetches)
  • NEXT_PUBLIC_EDITOR_ORIGIN (optional; only if editor mode is enabled)

2) Full the editor stack on Vercel (site + orchestrator + the editor)

Use this only if you want production the editor workflows.

Required code changes

Origin configuration and CORS were on this list and have since shipped. The editor reads its origins from window.__AVOCADO_CONFIG__ / VITE_* and derives its own origin from window.location.origin; the site reads NEXT_PUBLIC_EDITOR_ORIGIN and falls back to localhost only in development; the orchestrator’s CORS is an env-driven allowlist (ORCHESTRATOR_CORS_ORIGINS) that rejects anything unlisted. What remains genuinely open is persistence and the runtime model.
  1. Rework orchestrator persistence.
  • Current implementation persists to a local SQLite file (better-sqlite3).
  • Vercel functions are ephemeral and have read-only filesystems outside /tmp; a local SQLite file is not durable across invocations.
  • Move state to a network-accessible store (Postgres, Neon, Turso/libSQL, Redis/KV, etc.).
  1. Rework orchestrator runtime model for Vercel. Done. createOrchestrator() in @avocadostudio-ai/orchestrator-core returns a Web-standard (Request) => Promise<Response> handler you can mount as a Next.js route handler — see CMS adapters. The standalone Fastify app.listen(...) server is now one of two ways to run it, not the only one.
  • For Vercel, expose request handlers/functions instead of long-running process assumptions.
  1. Review streaming endpoint behavior (/chat/stream).
  • Ensure it fits your Vercel function execution limits and plan.
  • Add fallback to non-streaming /chat if needed.

Vercel projects

Set up separate projects:
  1. site project (Next.js)
  • Root: apps/site
  • Env: ORCHESTRATOR_URL, NEXT_PUBLIC_EDITOR_ORIGIN (if used)
  1. orchestrator project (API)
  • Root: apps/orchestrator
  • Env: OPENAI_API_KEY, model env vars, storage connection env vars, CORS allowlist
  1. Editor project (optional)
  • Root: apps/editor
  • Env: VITE_ORCHESTRATOR_URL, VITE_SITE_ORIGIN, and optionally VITE_PUBLISH_TOKEN, VITE_SITE_DRAFT_SECRET, VITE_ENABLE_PATCH_TRANSPORT

Minimal change list before first Vercel publish

If the goal is to publish quickly, do these first:
  1. Implement published rendering path in apps/site that does not require orchestrator.
  2. Disable/protect production the editor mode.
  3. Deploy apps/site only on Vercel.
Then add orchestrator/the editor deployment as phase 2.

Deferred (Phase 2, later)

  • Deploy apps/orchestrator on Vercel-compatible runtime.
  • Move orchestrator state to durable external storage.
  • Migrate the editor/site/orchestrator origins to env-only configuration.
  • Lock CORS to explicit allowlist.
  • Deploy apps/editor (if production editing is required).

Suggested rollout plan

  1. Phase 1: Public site only on Vercel (stable, low risk)
  2. Phase 2: Externalize orchestrator state storage
  3. Phase 3: Deploy orchestrator + the editor with locked CORS and env-driven origins

Use separate URLs/environments for production and the editor staging.

Production site (stable)

  • Vercel project/branch: main
  • URL: public production URL (for end users)
  • Env:
    • ORCHESTRATOR_URL unset
Behavior: serves committed apps/site/lib/published-content.json only.

Staging site (the editor preview target)

  • Vercel project or branch: beta-editor (recommended separate project URL)
  • URL: staging URL
  • Env:
    • ORCHESTRATOR_URL=https://<orchestrator-host>
    • SITE_PUBLISH_SITE_ID=<your site id>required whenever ORCHESTRATOR_URL is set, or the build fails in sync-published-content.mjs
    • NEXT_PUBLIC_EDITOR_ORIGIN=https://<editor-host>
Behavior: can read live draft content from orchestrator for editing.

Editor app

  • Vercel project root: apps/editor
  • Env:
    • VITE_SITE_ORIGIN=https://<staging-site-host> (not production)
    • VITE_ORCHESTRATOR_URL=https://<orchestrator-host>
    • VITE_PUBLISH_TOKEN=<same as orchestrator PUBLISH_TOKEN> (if enabled)

Orchestrator

  • Host on Render (or equivalent long-running host)
  • Env:
    • PUBLISH_GIT_BRANCH=beta-editor
    • ORCHESTRATOR_CORS_ORIGINS=https://<staging-site-host>,https://<editor-host>
Behavior: publish writes to beta branch, preventing accidental updates to production main.

Production troubleshooting

Block selector / overlay not working

The block selector requires a working postMessage channel between the editor iframe and site. Check these in order:
  1. Draft mode not enabled — without VITE_SITE_DRAFT_SECRET on the editor, the iframe URL uses __editor=1 which is ignored in production. Set VITE_SITE_DRAFT_SECRET on the editor and DRAFT_MODE_SECRET on site (matching values).
  2. No draft contexteditorMode is simply contentSource === "draft". It comes from Next draft mode or a valid secret, via resolveDraftContextCore. If neither is present, PreviewBridge never mounts and blocks have no click handlers. (NEXT_PUBLIC_ENABLE_EDITOR does not control this — it only gates the /catalogue route. Setting it alone changes nothing.)
  3. Origin mismatch in postMessageevent.origin never has a trailing slash. If NEXT_PUBLIC_EDITOR_ORIGIN or VITE_SITE_ORIGIN has a trailing slash, all messages are silently dropped. Always omit trailing slashes.
  4. Missing /api/editor/pages — the editor calls this on the site to seed the orchestrator with initial content. Without it, the orchestrator has no draft pages and the site shows “Draft unavailable”.
  5. CORS on orchestratorORCHESTRATOR_CORS_ORIGINS must include both the editor and site origins.

Required env vars (full stack)

Publishing from a site deployed on Vercel

Two things about publishing change the moment the site is on Vercel rather than on your machine.
  • The publish route is closed until you configure it. POST /api/editor/publish refuses every request with 401 under NODE_ENV=production when no publishSecret is set, and names PUBLISH_TOKEN in the refusal. Set the same value on the site project and on the orchestrator.
  • A deployed site cannot rewrite its own content file. The runtime filesystem is read-only outside /tmp, so a publish handler that writes a JSON file under the project — createJsonFilePublishHandler, or jsonFileAdapter({ writeOnPublish: true }) — works in next dev and fails after deploy. Publishing in production goes to something that persists: a CMS, a database, or a git commit that triggers a rebuild.

Orchestrator on Render

The orchestrator runs via tsx src/index.ts (not compiled JS). tsx is a production dependency. The build step is tsc --noEmit (typecheck only). Render build command: pnpm install --frozen-lockfile && pnpm --filter @ai-site-editor/orchestrator build.