/preview route required.
Related:
- Editor Quickstart — env vars, iframe URL pattern, smoke checks
- Custom Blocks — register your own component types alongside (or instead of) the built-in blocks
- Architecture — how the three services communicate
How it works in two helpers
The SDK collapses the entire integration into two factory functions. Most adopters need exactly two new files; nothing else changes in your project.
You wire both with your existing CMS / content fetchers (
getPage, getSlugs, getSiteConfig), then register the site with npx avocado-register. That’s the whole integration.
If you need fine-grained control instead, the low-level primitives section shows the underlying handlers (createBlocksHandler, createDraftEnableHandler, createDraftDisableHandler, fetchEditorPage, fetchEditorSlugs).
Walkthrough
1
Install the SDK
From your Next.js project root:Peer dependencies (
next ≥ 15, react ≥ 19) should already be in your project. The SDK has no other runtime dependencies.2
Mount the catch-all editor API route
Create This single file exposes:
app/api/editor/[...path]/route.ts:GET /api/editor/blocks— block manifest (auto-built from the SDK’s built-in registry, override viagetManifestfor custom blocks)GET /api/editor/pages—{ pages: PageDoc[] }for editor session bootstrapGET /api/editor/draft?secret=...&redirect=...— Draft Mode entry, validatessecretagainstDRAFT_MODE_SECRET, only allows internal redirectsGET /api/editor/draft/disable?redirect=...— Draft Mode exitPOST /api/editor/publish— receives published pages back from the editor
3
Replace your page route with createSitePage
Create (or replace)
app/[[...slug]]/page.tsx:createSitePage handles, in order:- Detecting Draft Mode via
next/headersand switching reads tofetchEditorPage/fetchEditorSlugs(which call the orchestrator) - Building site nav/header chrome from
getSiteConfig - Rendering blocks via the SDK’s
renderBlocksand the shared block library - Mounting the live
EditorOverlaywhen in editor mode - Falling back to your CMS data if the orchestrator is unreachable
- Returning a 404 (or “Draft unavailable”) fallback when no page exists for the slug
lib/my-cms.ts does not change — createSitePage calls into it.4
Register the site with the orchestrator
Make sure the orchestrator is running (The CLI (shipped inside
pnpm dev:orchestrator from the Avocado repo, or your hosted instance), then from your Next.js project directory:@ai-site-editor/site-sdk) will:- Generate a
DRAFT_MODE_SECRETif.env.localdoesn’t already have one (32 random bytes, hex-encoded). - Write
ORCHESTRATOR_URL,DRAFT_MODE_SECRET,NEXT_PUBLIC_DEFAULT_SITE_ID,NEXT_PUBLIC_SITE_NAME,NEXT_PUBLIC_EDITOR_ORIGINto.env.localif missing (existing values are never overwritten). - POST your site config to
${ORCHESTRATOR_URL}/sites/register.
npx avocado-register --help. Common ones: --id, --port, --orchestrator, --secret, --session, --purpose.After it succeeds, the site appears in the editor’s dashboard the next time you open or refresh http://localhost:4100.5
Verify the contract
Start your dev server, then run these from a second terminal. All four should pass:And one negative check that’s worth running by hand because it’s the security-critical one:If all five behave as shown, the integration is complete.
6
Open the editor and confirm round-trip
Open
http://localhost:4100. Your site should be in the dashboard. Click its tile, then send a simple edit from the chat panel like “change the hero headline to Hello world”. You should see:- The AI generate an operation
- The preview update inside the iframe
- An undo entry appear in the history
TypeScript types
The SDK re-exports the core types from@avocadostudio-ai/shared. Import what your fetchers need:
PageDoc has shape { id: string; slug: string; meta?: PageMeta; blocks: BlockInstance[] }. BlockInstance is { id: string; type: string; props: Record<string, unknown> }. See packages/shared/src/schemas.ts in the repo for the Zod schemas that back these types.
Block manifest
The manifest is what tells the editor which block types exist and what props each one accepts.createEditorApiHandler builds it automatically from the SDK’s built-in block registry — you only need to think about it if you have custom React components.
Example response shape from GET /api/editor/blocks:
getManifest function to createEditorApiHandler and the SDK uses yours instead of the built-in one.
Component matching
The editor never infers components from DOM class names. It matches by stabletype strings that must agree across three places:
Environment variables
The values written bynpx avocado-register into your project’s .env.local:
And in the editor itself (
apps/editor/.env, set by you), single-tenant build-time:
A mismatch between
VITE_SITE_DRAFT_SECRET (built into the editor) and DRAFT_MODE_SECRET (read by the site at runtime) is the single most common failure — avocado-register surfaces it as a warning, and the orchestrator’s /sites/register response includes a warnings array for the same reason.
Troubleshooting
Low-level primitives
IfcreateEditorApiHandler and createSitePage are too opinionated for your project — for example you have a custom routing layer, you mount the editor API at a non-standard path, or you need to compose draft mode with your own middleware — the same building blocks are exported individually:
{ GET, POST, OPTIONS } object you mount at any route you like. fetchEditorPage(slug, session, siteId) and fetchEditorSlugs(session, siteId) are the primitives createSitePage calls internally — use them directly inside your own page component if you need to compose them with other data sources.
The contract these primitives implement is the same one createEditorApiHandler mounts:
- Block manifest:
GET /api/editor/blocks(or wherever you mount it) - Pages snapshot:
GET /api/editor/pages - Draft enter:
GET /api/editor/draft?secret=...&redirect=/...— must validate the secret and reject non-internal redirects - Draft exit:
GET /api/editor/draft/disable?redirect=/... - Publish:
POST /api/editor/publish
VITE_SITE_ORIGIN and the bootstrap URL builder accordingly — the editor expects the standard paths by default.
Optional: dedicated /preview/* route group
If you want stronger isolation between published and draft content (e.g. a separate route group with its own middleware, layout, or feature flags), you can add a/preview/* route group that calls into fetchEditorPage directly. This is opt-in and not part of the standard onboarding path — most adopters don’t need it because Draft Mode cookies already give you per-request isolation.