Skip to main content
This is the canonical onboarding path for any Next.js 15 (App Router) site. For background, see Core Concepts and the Integration Overview. Goal: keep your existing routes serving published content, enable AI editing through Next.js Draft Mode cookies, and register the site so it shows up in the editor’s dashboard. No /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 app/api/editor/[...path]/route.ts:
This single file exposes:
  • GET /api/editor/blocks — block manifest (auto-built from the SDK’s built-in registry, override via getManifest for custom blocks)
  • GET /api/editor/pages{ pages: PageDoc[] } for editor session bootstrap
  • GET /api/editor/draft?secret=...&redirect=... — Draft Mode entry, validates secret against DRAFT_MODE_SECRET, only allows internal redirects
  • GET /api/editor/draft/disable?redirect=... — Draft Mode exit
  • POST /api/editor/publish — receives published pages back from the editor
Secret validation, internal-redirect enforcement, CORS preflight, and the draft cookie are all handled by the helper. You do not implement these yourself.
3

Replace your page route with createSitePage

Create (or replace) app/[[...slug]]/page.tsx:
createSitePage handles, in order:
  • Detecting Draft Mode via next/headers and switching reads to fetchEditorPage / fetchEditorSlugs (which call the orchestrator)
  • Building site nav/header chrome from getSiteConfig
  • Rendering blocks via the SDK’s renderBlocks and the shared block library
  • Mounting the live EditorOverlay when 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
Your existing lib/my-cms.ts does not change — createSitePage calls into it.
4

Register the site with the orchestrator

Make sure the orchestrator is running (pnpm dev:orchestrator from the Avocado repo, or your hosted instance), then from your Next.js project directory:
The CLI (shipped inside @ai-site-editor/site-sdk) will:
  1. Generate a DRAFT_MODE_SECRET if .env.local doesn’t already have one (32 random bytes, hex-encoded).
  2. Write ORCHESTRATOR_URL, DRAFT_MODE_SECRET, NEXT_PUBLIC_DEFAULT_SITE_ID, NEXT_PUBLIC_SITE_NAME, NEXT_PUBLIC_EDITOR_ORIGIN to .env.local if missing (existing values are never overwritten).
  3. POST your site config to ${ORCHESTRATOR_URL}/sites/register.
All flags: 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:
  1. The AI generate an operation
  2. The preview update inside the iframe
  3. An undo entry appear in the history
If anything’s wrong, jump to Troubleshooting.
Open-redirect risk on /api/editor/draft. The redirect query parameter accepts the destination after Draft Mode is enabled. If you bypass createEditorApiHandler and roll your own route, you must reject anything that isn’t an internal path starting with /. An unvalidated redirect=https://evil.com would let an attacker craft a phishing link that briefly visits your domain (granting it credibility) before bouncing victims to a malicious page. The SDK handler enforces this for you — that’s the main reason to use it instead of writing the route by hand.

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:
If the manifest is missing or the route returns 404, the editor falls back to degraded mode — read-only preview with text-only edits, no add/remove/reorder/update-props operations. Use this as your “is the SDK actually wired in?” canary: a present manifest unlocks the full editing experience. To register your own components, see Custom Blocks — you pass a 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 stable type strings that must agree across three places:
If a block type appears in content but not in the manifest, it still renders on the published site, but the editor refuses structural ops on that specific block (degraded for that type only — the rest of the page stays editable).

Environment variables

The values written by npx 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

If createEditorApiHandler 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:
Each factory returns a { 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
If you change the URL paths, update the editor’s 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.