Both produce the library-mode shape: the orchestrator runs inside your Next.js
app at
/api/avocado, and content/pages.json is the site. Bringing a real
site in — your components, your CMS, your content — is a different and larger
job. Start at bring your site in.Prerequisites
- Node.js 22+ — check with
node --version - A Next.js 15 or 16 project on the App Router, or an empty directory
- One LLM API key — Anthropic, OpenAI or Google Gemini
The same thing, as one shell block
No coding agent, or you would rather watch it happen? This is the same wiring in one paste, against a brand-new app. It was run end to end on Node 22 / npm 10 / Next 16.3.5; thesiteUrl, writeOnPublish and publishSecret lines it has
gained since are the ones create-avocado-site emits, which pnpm test:build
builds and serves on every run.
.env.local, then
start both processes.
Step by step
1. Install the packages
From your project directory:site-sdk is the integration surface — routes, page factory, markers,
publishing. orchestrator-core is the brain, and it is an optional peer of
the SDK, so it is not installed for you: a site that only renders blocks and
talks to a remote orchestrator does not need it, and you do. Both pull in
@avocadostudio-ai/blocks, @avocadostudio-ai/shared,
@avocadostudio-ai/preview-adapter and @avocadostudio-ai/richtext as
transitive dependencies.
2. Wrap your Next config
orchestrator-core carries native dependencies (better-sqlite3, sharp).
withAvocado sets serverExternalPackages, the matching server externals, and
transpilePackages together — serverExternalPackages alone is not enough,
because transpilePackages overrides it for a transitive dependency. See
server externals.
Then import the block stylesheet, at the top of app/globals.css:
3. Give the site some content
Avocado edits aPageDoc — a page with an ordered list of typed blocks. For
this quickstart, keep it in a JSON file.
content/pages.json
Hero and CTA are two of the 20 built-in block types, which is why this
renders with no components of your own. On a real site the blocks are your
React components, registered with a schema — see
custom blocks.
4. Mount the editor API route
onPublish — and, with it, publishSecret — when you want
publishing through this route.
5. Mount the orchestrator inside your app
siteName is what the editor greets you with. Leave it out and the editor
title-cases the site id instead, so a mount called my-shop is introduced as
“My Shop” — a reasonable guess, and a poor one for anything whose id is not its
name. There is also demoContent: true, which tells the editor this mount is
serving Avocado’s shipped demo pages and turns on the first-run suggestions
written against them; create-avocado-site sets it, and a real site should not.
This is library mode: the planner, the operations engine, the draft state and
the version log all run inside your Next.js app at /api/avocado. The adapter
is how it reads your content on a cold session — and, when someone publishes,
how it writes back. writeOnPublish is what gives jsonFileAdapter an
onPublish at all; it defaults to false, and without it Publish reports
success and leaves the file untouched.
createOrchestrator() is open in development and closed under
NODE_ENV=production when neither ACCESS_PASSWORD_HASH nor
ORCHESTRATOR_ACCESS_TOKEN is set, and no auth hook is passed. That is
deliberate — an unauthenticated publish endpoint on your own domain is not a
default anyone should reach by forgetting something. Local next dev needs
nothing. Full detail: security and access.6. Render the page
generateMetadata derives a page’s title, description and Open Graph tags from
the page itself. Three tags it cannot derive, because none of them is knowable
without knowing where the site lives: <link rel="canonical">, og:url, and an
og:image resolved to an absolute URL. siteUrl is how you say. Leave it out
and the SDK emits none of the three rather than guessing — a wrong canonical is
worse than an absent one — and a relative ogImage goes out relative, which is
correct in an <img src> and ignored by every social crawler, so the page’s
cards render as a bare text link. Read it from an environment variable at the
call site, as above, so a preview deployment describes itself rather than
claiming to be production.
mode defaults to "auto": one route handles both the published page and the
editor preview, with no middleware or proxy to set up. It is not statically
rendered, because deciding between the two modes means reading searchParams on
every request. A production site wants mode: "static" plus a separate
mode: "preview" route — see
Next.js integration.
7. Set your key
ORCHESTRATOR_URL tells the SDK’s server-side draft fetch where the
orchestrator is. A library-mode mount registers itself, so this is usually
redundant — set it anyway, because the fallback when nothing is registered is
http://127.0.0.1:4200, and whatever else is listening there will answer.
NEXT_PUBLIC_SITE_URL is the origin createSitePage({ siteUrl }) reads. Point
it at your real domain when you deploy; the canonical link and the social cards
are only as right as this value.
OPENAI_API_KEY and GOOGLE_GENAI_API_KEY work in the same slot. The editor’s
model picker offers whichever keys are present.
Every variable Avocado reads: environment reference.
Troubleshooting
pnpm dev fails with a better-sqlite3 or sharp error. Your Next config
is not wrapped. Apply withAvocado (step 2) — the
native binaries must stay external to the bundle.
The chat answers, but nothing changes, and the model picker is empty. No
provider key reached the orchestrator. Check
curl http://localhost:3000/api/avocado/status/planner: availableProviders
should list your provider, and plannerSource should not be "demo". If it is,
the key is missing from .env.local or the dev server has not been restarted
since you added it.
The preview iframe is blank. Open http://localhost:3000 in a normal tab
first. The editor frames your real site, so if the site is not serving, the
preview has nothing to show.
The preview shows the published page and never your edits. The property
panel saying “this block is not in the page the orchestrator returned” is the
same symptom: the editor is talking to a different orchestrator than the one
inside your site. Confirm you passed
--orchestrator http://localhost:3000/api/avocado, or set the site’s
Orchestrator URL in the editor’s site settings.
The editor says nothing was written, and content/pages.json is unchanged.
The adapter has no onPublish, so there was nowhere to put the pages — your
edits are still there as a draft. Pass writeOnPublish: true to
jsonFileAdapter (step 5). The API
answers this one as ok: true, written: false with the same thing in reason,
so a script that checks only ok will not notice.
Publishing answers 401 or 409. A 401 is POST /api/editor/publish: either
it is running under NODE_ENV=production with no publishSecret — the body’s
reason names PUBLISH_TOKEN — or the x-publish-token it received does not
match the one configured. A 409 means the publish would have removed every
page, and the reason says what it protected; resend with "allowDelete": true
only if emptying the site is genuinely what you meant.
A production build serves the site but the editor loads nothing.
createOrchestrator() is closed under NODE_ENV=production with no credential.
curl http://localhost:3000/api/avocado/auth/status answers mode: "closed"
and a reason naming ACCESS_PASSWORD_HASH and ORCHESTRATOR_ACCESS_TOKEN;
set one of them. See security and access.
Node version errors. Install Node 22 with your version manager —
fnm install 22, nvm install 22, or mise use node@22.
Something deeper. See
chat troubleshooting.