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)
What Phase 1 already gives you
These three shipped and need no work on your part:apps/siterenders published content from a committedlib/published-content.jsonwhenORCHESTRATOR_URLis unset — no orchestrator needed at runtime.- Draft fetches use
Promise.allSettled, so an unreachable orchestrator degrades to published content instead of failing the render. __editor=1authorizes nothing in production; a valid draft secret or a draft-mode cookie is required.
apps/site as a single Vercel project.
Current architecture (from this repo)
apps/siteis a Next.js app.apps/orchestratoris a Fastify API that persists session state to a local SQLite database viabetter-sqlite3(.data/orchestrator.db+ WAL).apps/editoris a Vite app. Origins come fromwindow.__AVOCADO_CONFIG__, thenVITE_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:- Public website only (recommended for launch)
- Full the editor + orchestration stack on Vercel
1) Public website only (recommended)
Use this if you want a stable public site and do not need live editing in production.Required changes
- Decouple
apps/sitefrom draft API at runtime.
- Today,
apps/sitefetches 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).
- Add a published content source for site rendering.
- Option A: Use
demoPublishedPages()frompackages/sharedas initial published content. - Option B: Load from a real storage backend (recommended long term).
- Add production fallback behavior.
- If orchestrator is unavailable, site should still render published pages (not
Page not found).
- Restrict or disable editor mode in production.
__editor=1should be disabled or protected.- Avoid exposing editor bridge behavior publicly by default.
Vercel config
Create one Vercel project forapps/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.- 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.).
Rework orchestrator runtime model for Vercel.Done.createOrchestrator()in@avocadostudio-ai/orchestrator-corereturns a Web-standard(Request) => Promise<Response>handler you can mount as a Next.js route handler — see CMS adapters. The standalone Fastifyapp.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.
- Review streaming endpoint behavior (
/chat/stream).
- Ensure it fits your Vercel function execution limits and plan.
- Add fallback to non-streaming
/chatif needed.
Vercel projects
Set up separate projects:siteproject (Next.js)
- Root:
apps/site - Env:
ORCHESTRATOR_URL,NEXT_PUBLIC_EDITOR_ORIGIN(if used)
orchestratorproject (API)
- Root:
apps/orchestrator - Env:
OPENAI_API_KEY, model env vars, storage connection env vars, CORS allowlist
- Editor project (optional)
- Root:
apps/editor - Env:
VITE_ORCHESTRATOR_URL,VITE_SITE_ORIGIN, and optionallyVITE_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:- Implement published rendering path in
apps/sitethat does not require orchestrator. - Disable/protect production the editor mode.
- Deploy
apps/siteonly on Vercel.
Deferred (Phase 2, later)
- Deploy
apps/orchestratoron 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
- Phase 1: Public site only on Vercel (stable, low risk)
- Phase 2: Externalize orchestrator state storage
- Phase 3: Deploy orchestrator + the editor with locked CORS and env-driven origins
Recommended Beta Split (Avoid Production Overwrite)
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_URLunset
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 wheneverORCHESTRATOR_URLis set, or the build fails insync-published-content.mjsNEXT_PUBLIC_EDITOR_ORIGIN=https://<editor-host>
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-editorORCHESTRATOR_CORS_ORIGINS=https://<staging-site-host>,https://<editor-host>
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:-
Draft mode not enabled — without
VITE_SITE_DRAFT_SECRETon the editor, the iframe URL uses__editor=1which is ignored in production. SetVITE_SITE_DRAFT_SECRETon the editor andDRAFT_MODE_SECRETon site (matching values). -
No draft context —
editorModeis simplycontentSource === "draft". It comes from Next draft mode or a validsecret, viaresolveDraftContextCore. If neither is present,PreviewBridgenever mounts and blocks have no click handlers. (NEXT_PUBLIC_ENABLE_EDITORdoes not control this — it only gates the/catalogueroute. Setting it alone changes nothing.) -
Origin mismatch in postMessage —
event.originnever has a trailing slash. IfNEXT_PUBLIC_EDITOR_ORIGINorVITE_SITE_ORIGINhas a trailing slash, all messages are silently dropped. Always omit trailing slashes. -
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”. -
CORS on orchestrator —
ORCHESTRATOR_CORS_ORIGINSmust 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/publishrefuses every request with 401 underNODE_ENV=productionwhen nopublishSecretis set, and namesPUBLISH_TOKENin 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, orjsonFileAdapter({ writeOnPublish: true })— works innext devand 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 viatsx 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.