Skip to main content
This page is for the self-hoster putting a site that uses Avocado Studio on Vercel. Vercel runs your site and a static build of the editor well. The part that needs care is the orchestrator’s state: drafts, undo history and the version log live in a SQLite file, and a serverless function has no disk that survives.

What runs where

Library mode on Vercel

createOrchestrator() returns a Web-standard handler, and mounting it as a Next.js route works on Vercel: requests are answered, the access gate applies, and a page rendered by the same function reads its drafts in-process. What does not hold is the state behind it.
  • A deployed function’s filesystem is read-only outside /tmp, and /tmp belongs to one instance and disappears with it.
  • Vercel runs as many instances as traffic needs. Each one would keep its own drafts.
So an edit can apply, show in the preview, and then be gone on the next request or after the next cold start. Nothing errors. GET /status/planner reports persistence.ok: false with the reason when the store could not open. See state and backups.
For editing in production, run the orchestrator where it has a disk. Either run the standalone orchestrator and point the site at it, or host the Next.js app that mounts library mode on a platform with a persistent volume and a single instance. A Vercel deployment of the same app can still serve the public site.
When you do mount library mode on Vercel, for example for a preview environment whose drafts may be thrown away:
  • Pass waitUntil from @vercel/functions, or Next’s after(), to createOrchestrator({ waitUntil }). Background check runs after a publish or an edit are handed to it, so they are not cut off when the response is sent.
  • Chat replies stream over server-sent events, so the function’s maximum duration bounds the longest chat turn.
  • Set ACCESS_PASSWORD_HASH or ORCHESTRATOR_ACCESS_TOKEN. Under NODE_ENV=production, library mode with neither refuses every request.

Site on Vercel, orchestrator elsewhere

The site reads drafts from the orchestrator over HTTP and publishes through its own /api/editor/publish. Write every origin without a trailing slash.

Publishing from a site 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.

The editor on Vercel

The editor is a static Vite app in apps/editor, and its vercel.json already rewrites every path to index.html.
  • Root directory: apps/editor
  • Build command: pnpm --filter @ai-site-editor/editor build
  • Output directory: dist
VITE_* values are compiled into JavaScript that anyone can download. Do not set VITE_SITE_DRAFT_SECRET or VITE_PUBLISH_TOKEN on a public editor. Put DRAFT_MODE_SECRET, PUBLISH_TOKEN and ACCESS_PASSWORD_HASH on the orchestrator instead. After sign-in, the editor fetches the secret and the token from GET /editor/credentials. That route answers 403 in production until a credential is configured.
The alternative to a build is the prebuilt editor in @avocadostudio-ai/cli. Run avocado-studio start --host 0.0.0.0 on any Node 22 host. On a non-loopback bind it leaves the secrets out of the page for the same reason. See CLI and packages.

This repository’s demo site

The steps above are for your own site. Deploying apps/site from this repository has two details of its own:
  • Root directory apps/site. Its build runs scripts/sync-published-content.mjs and then next build.
  • With ORCHESTRATOR_URL unset, the build keeps the committed lib/published-content.json and the site needs no orchestrator at runtime. With it set, SITE_PUBLISH_SITE_ID is required or the build fails, and SITE_PUBLISH_SESSION defaults to dev.
NEXT_PUBLIC_ENABLE_EDITOR=1 only exposes the /catalogue route. It has no effect on draft mode or on the preview bridge. To keep editing away from production, give the editor a separate staging site to frame, and point the git publish target at a staging branch with PUBLISH_GIT_BRANCH.

Orchestrator from source

The standalone orchestrator can also run from source on any Node host with a persistent disk, without the Docker image. It runs TypeScript directly with tsx, which is a production dependency, and its build step is a typecheck.
  • Node 22 or later
  • Root directory: apps/orchestrator
  • Build: pnpm install --frozen-lockfile && pnpm --filter @ai-site-editor/orchestrator build
  • Start: pnpm --filter @ai-site-editor/orchestrator start, which runs tsx src/index.ts
  • Mount the persistent disk where ORCHESTRATOR_DB_FILE points
  • One instance only

Production troubleshooting

The preview shows the published page, never the draft. Check these in order:
  1. The site’s DRAFT_MODE_SECRET differs from the one the editor uses. The preview degrades to published content rather than failing.
  2. The editor could not fetch its credentials. A public editor needs ACCESS_PASSWORD_HASH, DRAFT_MODE_SECRET and PUBLISH_TOKEN on the orchestrator, or GET /editor/credentials answers 403.
  3. The site reads drafts from a different orchestrator, or with no access token where one is required. A 401 on the draft read is logged on the site.
Clicking in the preview selects nothing. Click-to-select is behind the selection-mode toggle, which is off on first load. If it is on and still nothing happens, check that the editor’s origin is in NEXT_PUBLIC_EDITOR_ORIGIN or AVOCADO_EDITOR_ORIGINS. The bridge only talks to an origin on that list. The editor says it cannot reach the orchestrator, but curl gets a 200. That is CORS. ORCHESTRATOR_CORS_ORIGINS on a standalone orchestrator, or corsOrigins in library mode, must include the editor’s origin. The editor has no pages to edit. The editor seeds the orchestrator from the site’s /api/editor/pages. Without that route, the orchestrator has no draft pages for the site. Edits vanish between requests or after a deploy. The orchestrator’s state is not on a persistent disk, or more than one instance is running. See library mode on Vercel.