One command
node_modules is Next 15.5.15, React 19, the
five Avocado runtime packages — site-sdk, orchestrator-core, blocks,
shared, preview-adapter — and the editor CLI as a dev dependency. Node 22 or
newer; the SQLite binding ships a prebuilt binary for it, and older Node falls
through to compiling from source.
Both ports are chosen at scaffold time from what is actually free on your
machine, starting at 3000 for the site and 4100 for the editor, so this runs
happily beside whatever else you have going.
npm run dev then starts two processes and prints a banner:
The
?siteId= is load-bearing, not decoration. The CLI serves a prebuilt
editor bundle and injects the orchestrator and preview origins into it, but not
a site id — so without one the bundle falls back to its own built-in default and
asks for a session this project never configured. It happens to render anyway,
because the orchestrator bootstraps an unknown session from the adapter and gets
the same nine pages. That is exactly what makes it worth pinning: the demo looks
correct while the id in the URL and the id in the route file are different
strings. The id is derived from your directory name, so my-site/ gets
siteId=my-site, and the generated route file agrees.What to click, and what each click proves
You land on the Avocado Hub: nine pages, sixty blocks, and all eighteen built-in page block types on screen. The two built-in types missing from it are the site header and the footer, which belong to the site rather than to a page — so every type a page can hold is somewhere in here, already filled with real copy.1
Switch pages in the preview
Nine slugs, from a home page of thirteen blocks down to four-block pages
like
/oranges and /olives. Every block type you would have to implement
on your own site is already rendering in one of them.2
Turn on the element picker, then click a block
The arrow-and-sparkle button in the chat composer — hover it and it reads
Select an element — toggles click-to-select. Click a heading, an image, a card. The overlay
highlights the block and the chat learns what you have in mind. Escape turns
it back off.This proves the part of an integration that is hardest to get right: a DOM
node in the rendered page maps back to a typed block in the content.
3
Edit a field in the property panel
With a block selected, the panel on the side lists its real fields and
writes real values. No model is involved — the panel drives the same typed
operations the chat does, straight to the orchestrator.
4
Undo it
Every applied change carries an undo in the chat entry that produced it, and
a global undo and redo sit directly above the message box (Ctrl+Z, or Cmd+Z
on a Mac). Undo is server-side history, not a browser trick.
5
Publish
Publishing is a separate, explicit step. The editor posts to the
orchestrator, which hands the current draft to the adapter, which rewrites
content/pages.json. A measured run answered
{"status":"ready","written":true,"count":9}, the file on disk changed, and
the new text appeared in the rendered HTML.6
Then try the chat
Chat is the one thing that wants a model — and it answers without one. That
is the next section.
No API key is needed to look around
Everything above works with nothing configured. Chat does too, with a smaller promise, and the editor says so rather than failing. With no key, a notice sits directly above the message box saying so, and naming the full path of the.env.local to add a key to. The chat is answered by a
deterministic planner: a short list of literal matchers, not a small model. So
the first-screen suggestions change to the ones that planner can actually
execute:
Three at most, and each one really edits the page — which is the claim the first
screen is making. A clean-room run fired the keyless suggestions on all nine
pages: 26 of 26 clicks produced at least one operation and every one applied.
The honest limit is the shape of the planner, not its size. It matches literal
phrases — “rich text”, “add testimonials”, “set the meta description to …” — so
a sentence of your own invention usually comes back asking for a key rather than
guessing. The suggestions are curated around that, deliberately.
What a key costs
Chat with a real planner behind it is the thing worth evaluating, and it runs on your key. Avocado never resells tokens, which also means nothing between you and the provider is metering the spend for you.The three keys
One line in.env.local, in the project root — the same folder as
package.json:
Restart the dev server after adding one. Keys are read at startup, so a
running server will not pick one up, and the symptom is a key that appears to
have been ignored.
What a click costs
With a key, the first screen offers different suggestions: Translate the whole page to German always, one add-a-section suggestion chosen from what is already on the page, and Turn the hero into a bold call-to-action when the page has a hero. They are not priced the same, and nothing in the product says so. From the orchestrator’s own per-turn telemetry in a clean-room run on 0.11.3:
Two things in that table matter more than the numbers. The three
Add …
chips are genuinely free, because the deterministic planner answers them
without calling anything. And one chip is most of the bill — the one a
curious person is most likely to press first. Translating a page rewrites every
text-bearing block on it, so its cost scales with the page rather than with the
sentence you typed; $0.1685 is its price on the cheapest page in the demo,
and a four-block page is the cheapest thing it can be asked to do. Nothing in
the UI distinguishes the free chips from that one.
Keep a spend limit on the key you paste in, as you would for anything else that
calls a model on your behalf.
What you will notice
Four things read as defects on a first run. All four are working as designed, and each one looks broken if nobody says so first. Click-to-select is off until you turn it on. The picker defaults to off, so a correct install looks inert: you click a heading in the preview and nothing happens. The cursor button in the composer is the switch. It defaults off because the preview is a live site — links, tabs and carousels should work when you are reading rather than editing — and a picker that intercepts every click makes the page impossible to browse. The first render of the editor takes a few seconds. On a cold.next, the
editor’s first load of the site measured 3.83 seconds to first byte while Next
compiled the route. Warm, the same request runs in 45 to 73 ms. The first paint
is the compiler, not the product. The cold load itself is clean: 26 requests,
none of them a failure.
Publishing is a separate step, and the site proves it. Open
localhost:3000 directly and you see the published page; the draft appears
only under an editor context — the URL the editor’s preview frame uses. So
after an edit, the plain site is meant to look unchanged until you publish, and
content/pages.json is meant to be byte-identical. Both are true, and both are
how you check that publish did something.
This paragraph used to say the opposite — that development resolves an editor
context for every request, so the bare URL shows unpublished edits. That was
true once and is not now, and it was the worst thing on this page to get wrong:
it told a reader to distrust the one check that tells them whether publish
worked.
npm run build && npm start gives you a site that renders and an editor that
cannot load. That is the production gate, and it is deliberate. The
orchestrator mounted inside your app can edit and publish your content, so with
NODE_ENV=production and no credential configured it refuses every request
rather than running open. A default that is convenient in development and
catastrophic in production has to pick production.
It says so once in the build output and once again at boot:
next build that is a warning, with an added clause noting it was
evaluated at build time and that the variables belong in the deployment’s
environment. At runtime it is an error, because then it means requests really
are being refused. Three things open the gate: ACCESS_PASSWORD_HASH (the
sha256 of a password the editor prompts for), ORCHESTRATOR_ACCESS_TOKEN (a
fixed bearer token, for scripts and CI), or an auth hook passed to
createOrchestrator in code — including auth: () => true, which is a line you
type on purpose and cannot arrive at by omission. A few paths stay open either
way, because they answer before any caller could hold a credential: /health,
/auth/status, /auth/verify, and the generated-images the rendered page loads
in <img> tags.
Set one of those and the editor reaches the password screen instead of the
error.
That sentence is newer than it sounds, and it took three releases to make true.
A production deployment has to be told where its editor lives, and each of the
three ways that could go wrong went wrong in turn: the generated orchestrator
route was never told at all (0.11.2), the answer omitted a header the editor
sends (0.11.3), and then the right answer went only to one of the two ways the
editor’s address can be spelled (0.11.4). Every one of them had the same
symptom — the editor reporting it could not reach a server that was answering
200 to curl the whole time — because a response a browser rejects is
indistinguishable, from inside the browser, from a server that is down.
A project scaffolded on 0.11.4 or later needs none of this explained: it writes
EDITOR_CORS_ORIGINS, NEXT_PUBLIC_EDITOR_ORIGIN and
ORCHESTRATOR_CORS_ORIGINS into .env.local with a comment on each saying
which code reads it, and localhost and 127.0.0.1 are treated as the same
address wherever you write either. An older project needs those lines added by
hand — see the changelog for which does what, and
publishing.
What a production build is good for is seeing what a crawler gets: per-page
titles, descriptions, Open Graph tags, a canonical link, and a real 404 on an
unknown slug. In this demo those come out identically in development, so
npm start confirms them rather than being the only way to see them. It is not
the way to try the editor. For that, stay in npm run dev, which needs none of
the above.
Where to go next
Wire it into an existing site
The quickstart is the other half of this story: a prompt for your coding
agent, or the same wiring as one shell block, against a Next.js app you
already have.
Bring your real site in
Your components, your CMS, your content. Three honest paths, measured in
days rather than minutes.
The SDK
Routes, the page factory, custom blocks, CMS adapters, publishing — every
seam an integration has to satisfy.
How it works
What happens between your sentence and the changed page, if you would rather
understand the model before wiring anything.